diff --git a/apps/desktop/src-tauri/src/deeplink_actions.rs b/apps/desktop/src-tauri/src/deeplink_actions.rs index dbd90f667f..d2dac2fd8b 100644 --- a/apps/desktop/src-tauri/src/deeplink_actions.rs +++ b/apps/desktop/src-tauri/src/deeplink_actions.rs @@ -6,7 +6,48 @@ use std::path::{Path, PathBuf}; use tauri::{AppHandle, Manager, Url}; use tracing::trace; -use crate::{App, ArcLock, recording::StartRecordingInputs, windows::ShowCapWindow}; +use crate::{ + App, ArcLock, feeds::microphone::MicrophoneFeed, recording::StartRecordingInputs, + windows::ShowCapWindow, +}; + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct DeepLinkRecordingStatus { + pub is_recording: bool, + pub is_paused: bool, + pub recording_mode: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct DeepLinkDevices { + pub cameras: Vec, + pub microphones: Vec, + pub screens: Vec, + pub windows: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct DeepLinkCamera { + pub name: String, + pub id: String, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct DeepLinkScreen { + pub name: String, + pub id: String, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct DeepLinkWindow { + pub name: String, + pub owner_name: String, +} #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] @@ -26,6 +67,21 @@ pub enum DeepLinkAction { mode: RecordingMode, }, StopRecording, + PauseRecording, + ResumeRecording, + TogglePauseRecording, + RestartRecording, + TakeScreenshot { + capture_mode: CaptureMode, + }, + SetMicrophone { + label: Option, + }, + SetCamera { + id: Option, + }, + ListDevices, + GetStatus, OpenEditor { project_path: PathBuf, }, @@ -104,6 +160,21 @@ impl TryFrom<&Url> for DeepLinkAction { } } +fn resolve_capture_target(capture_mode: &CaptureMode) -> Result { + match capture_mode { + CaptureMode::Screen(name) => cap_recording::screen_capture::list_displays() + .into_iter() + .find(|(s, _)| s.name == *name) + .map(|(s, _)| ScreenCaptureTarget::Display { id: s.id }) + .ok_or_else(|| format!("No screen with name \"{}\"", name)), + CaptureMode::Window(name) => cap_recording::screen_capture::list_windows() + .into_iter() + .find(|(w, _)| w.name == *name) + .map(|(w, _)| ScreenCaptureTarget::Window { id: w.id }) + .ok_or_else(|| format!("No window with name \"{}\"", name)), + } +} + impl DeepLinkAction { pub async fn execute(self, app: &AppHandle) -> Result<(), String> { match self { @@ -119,18 +190,7 @@ impl DeepLinkAction { crate::set_camera_input(app.clone(), state.clone(), camera).await?; crate::set_mic_input(state.clone(), mic_label).await?; - let capture_target: ScreenCaptureTarget = match capture_mode { - CaptureMode::Screen(name) => cap_recording::screen_capture::list_displays() - .into_iter() - .find(|(s, _)| s.name == name) - .map(|(s, _)| ScreenCaptureTarget::Display { id: s.id }) - .ok_or(format!("No screen with name \"{}\"", &name))?, - CaptureMode::Window(name) => cap_recording::screen_capture::list_windows() - .into_iter() - .find(|(w, _)| w.name == name) - .map(|(w, _)| ScreenCaptureTarget::Window { id: w.id }) - .ok_or(format!("No window with name \"{}\"", &name))?, - }; + let capture_target = resolve_capture_target(&capture_mode)?; let inputs = StartRecordingInputs { mode, @@ -146,6 +206,70 @@ impl DeepLinkAction { DeepLinkAction::StopRecording => { crate::recording::stop_recording(app.clone(), app.state()).await } + DeepLinkAction::PauseRecording => { + crate::recording::pause_recording(app.clone(), app.state()).await + } + DeepLinkAction::ResumeRecording => { + crate::recording::resume_recording(app.clone(), app.state()).await + } + DeepLinkAction::TogglePauseRecording => { + crate::recording::toggle_pause_recording(app.clone(), app.state()).await + } + DeepLinkAction::RestartRecording => { + crate::recording::restart_recording(app.clone(), app.state()) + .await + .map(|_| ()) + } + DeepLinkAction::TakeScreenshot { capture_mode } => { + let capture_target = resolve_capture_target(&capture_mode)?; + + crate::recording::take_screenshot(app.clone(), capture_target) + .await + .map(|_| ()) + } + DeepLinkAction::SetMicrophone { label } => { + let state = app.state::>(); + crate::set_mic_input(state, label).await + } + DeepLinkAction::SetCamera { id } => { + let state = app.state::>(); + crate::set_camera_input(app.clone(), state, id).await + } + DeepLinkAction::ListDevices => { + let devices = get_available_devices(); + let json = serde_json::to_string(&devices).map_err(|e| e.to_string())?; + println!("CAP_DEEPLINK_RESPONSE:{}", json); + Ok(()) + } + DeepLinkAction::GetStatus => { + let state = app.state::>(); + let app_state = state.read().await; + let status = if let Some(recording) = app_state.current_recording() { + let is_paused = recording.is_paused().await.unwrap_or(false); + let mode = match recording { + crate::recording::InProgressRecording::Instant { .. } => { + Some("instant".to_string()) + } + crate::recording::InProgressRecording::Studio { .. } => { + Some("studio".to_string()) + } + }; + DeepLinkRecordingStatus { + is_recording: true, + is_paused, + recording_mode: mode, + } + } else { + DeepLinkRecordingStatus { + is_recording: false, + is_paused: false, + recording_mode: None, + } + }; + let json = serde_json::to_string(&status).map_err(|e| e.to_string())?; + println!("CAP_DEEPLINK_RESPONSE:{}", json); + Ok(()) + } DeepLinkAction::OpenEditor { project_path } => { crate::open_project_from_path(Path::new(&project_path), app.clone()) } @@ -155,3 +279,37 @@ impl DeepLinkAction { } } } + +fn get_available_devices() -> DeepLinkDevices { + let cameras: Vec = cap_camera::list_cameras() + .map(|c| DeepLinkCamera { + name: c.display_name().to_string(), + id: c.device_id().to_string(), + }) + .collect(); + + let microphones: Vec = MicrophoneFeed::list().keys().cloned().collect(); + + let screens: Vec = cap_recording::screen_capture::list_displays() + .into_iter() + .map(|(s, _)| DeepLinkScreen { + name: s.name, + id: s.id.to_string(), + }) + .collect(); + + let windows: Vec = cap_recording::screen_capture::list_windows() + .into_iter() + .map(|(w, _)| DeepLinkWindow { + name: w.name, + owner_name: w.owner_name, + }) + .collect(); + + DeepLinkDevices { + cameras, + microphones, + screens, + windows, + } +} diff --git a/extensions/raycast/README.md b/extensions/raycast/README.md new file mode 100644 index 0000000000..fff7735d5b --- /dev/null +++ b/extensions/raycast/README.md @@ -0,0 +1,54 @@ +# Cap Raycast Extension + +Control [Cap](https://cap.so) screen recording directly from Raycast. + +## Features + +- **Start Recording** - Start a new screen or window recording (instant or studio mode) +- **Stop Recording** - Stop the current recording +- **Pause Recording** - Pause the current recording +- **Resume Recording** - Resume a paused recording +- **Toggle Pause** - Toggle pause/resume on the current recording +- **Take Screenshot** - Capture a screenshot of a screen or window +- **Recording Status** - Check the current recording status +- **Open Settings** - Open Cap settings + +## Requirements + +- [Cap](https://cap.so) desktop app must be installed and running +- macOS + +## Installation + +1. Clone this repository +2. Navigate to the `extensions/raycast` directory +3. Run `npm install` +4. Run `npm run dev` to start development mode + +## How It Works + +This extension uses Cap's deeplink API to control the app. Commands are sent via the `cap-desktop://` URL scheme. + +## Deeplink Format + +``` +cap-desktop://action?value= +``` + +### Available Actions + +| Action | Description | +|--------|-------------| +| `get_status` | Get current recording status | +| `list_devices` | List available cameras, microphones, screens, and windows | +| `start_recording` | Start a new recording | +| `stop_recording` | Stop the current recording | +| `pause_recording` | Pause the current recording | +| `resume_recording` | Resume a paused recording | +| `toggle_pause_recording` | Toggle pause state | +| `restart_recording` | Restart the current recording | +| `take_screenshot` | Take a screenshot | +| `set_microphone` | Switch microphone | +| `set_camera` | Switch camera | +| `open_settings` | Open Cap settings | +| `open_editor` | Open a project in the editor | diff --git a/extensions/raycast/assets/icon.png b/extensions/raycast/assets/icon.png new file mode 100644 index 0000000000..718226caf2 Binary files /dev/null and b/extensions/raycast/assets/icon.png differ diff --git a/extensions/raycast/package.json b/extensions/raycast/package.json new file mode 100644 index 0000000000..c989123560 --- /dev/null +++ b/extensions/raycast/package.json @@ -0,0 +1,96 @@ +{ + "$schema": "https://www.raycast.com/schemas/extension.json", + "name": "cap", + "title": "Cap", + "description": "Control Cap screen recording from Raycast", + "icon": "icon.png", + "author": "cap", + "categories": ["Productivity", "Applications"], + "license": "MIT", + "commands": [ + { + "name": "start-recording", + "title": "Start Recording", + "subtitle": "Cap", + "description": "Start a new screen recording", + "mode": "view", + "keywords": ["record", "capture", "screen"] + }, + { + "name": "stop-recording", + "title": "Stop Recording", + "subtitle": "Cap", + "description": "Stop the current recording", + "mode": "no-view", + "keywords": ["stop", "end", "finish"] + }, + { + "name": "pause-recording", + "title": "Pause Recording", + "subtitle": "Cap", + "description": "Pause the current recording", + "mode": "no-view", + "keywords": ["pause", "hold"] + }, + { + "name": "resume-recording", + "title": "Resume Recording", + "subtitle": "Cap", + "description": "Resume a paused recording", + "mode": "no-view", + "keywords": ["resume", "continue"] + }, + { + "name": "toggle-pause", + "title": "Toggle Pause", + "subtitle": "Cap", + "description": "Toggle pause/resume on the current recording", + "mode": "no-view", + "keywords": ["toggle", "pause", "resume"] + }, + { + "name": "take-screenshot", + "title": "Take Screenshot", + "subtitle": "Cap", + "description": "Take a screenshot of a screen or window", + "mode": "view", + "keywords": ["screenshot", "capture", "snap"] + }, + { + "name": "recording-status", + "title": "Recording Status", + "subtitle": "Cap", + "description": "Show the current recording status", + "mode": "no-view", + "keywords": ["status", "state"] + }, + { + "name": "open-settings", + "title": "Open Settings", + "subtitle": "Cap", + "description": "Open Cap settings", + "mode": "no-view", + "keywords": ["settings", "preferences", "config"] + } + ], + "dependencies": { + "@raycast/api": "^1.87.0", + "@raycast/utils": "^1.17.0" + }, + "devDependencies": { + "@raycast/eslint-config": "^1.0.11", + "@types/node": "22.10.2", + "@types/react": "19.0.2", + "eslint": "^9.16.0", + "prettier": "^3.4.2", + "typescript": "^5.7.2" + }, + "scripts": { + "build": "ray build --skip-types -e dist -o dist", + "dev": "ray develop", + "fix-lint": "ray lint --fix", + "lint": "ray lint", + "prepublishOnly": "echo \"\\n\\nIt seems like you are trying to publish the Raycast extension to npm.\\n\\nIf you did intend to publish it to npm, remove the \\`prepublishOnly\\` script and rerun \\`npm publish\\` again.\\nIf you wanted to publish it to the Raycast Store instead, use \\`npm run publish\\` instead.\\n\\n\" && exit 1", + "publish": "npx @raycast/api@latest publish" + } +} diff --git a/extensions/raycast/raycast-env.d.ts b/extensions/raycast/raycast-env.d.ts new file mode 100644 index 0000000000..515aa93934 --- /dev/null +++ b/extensions/raycast/raycast-env.d.ts @@ -0,0 +1,52 @@ +/// + +/* 🚧 🚧 🚧 + * 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 = {} + +/** Preferences accessible in all the extension's commands */ +declare type Preferences = ExtensionPreferences + +declare namespace Preferences { + /** Preferences accessible in the `start-recording` command */ + export type StartRecording = ExtensionPreferences & {} + /** Preferences accessible in the `stop-recording` command */ + export type StopRecording = ExtensionPreferences & {} + /** Preferences accessible in the `pause-recording` command */ + export type PauseRecording = ExtensionPreferences & {} + /** Preferences accessible in the `resume-recording` command */ + export type ResumeRecording = ExtensionPreferences & {} + /** Preferences accessible in the `toggle-pause` command */ + export type TogglePause = ExtensionPreferences & {} + /** Preferences accessible in the `take-screenshot` command */ + export type TakeScreenshot = ExtensionPreferences & {} + /** Preferences accessible in the `recording-status` command */ + export type RecordingStatus = ExtensionPreferences & {} + /** Preferences accessible in the `open-settings` command */ + export type OpenSettings = ExtensionPreferences & {} +} + +declare namespace Arguments { + /** Arguments passed to the `start-recording` command */ + export type StartRecording = {} + /** Arguments passed to the `stop-recording` command */ + export type StopRecording = {} + /** Arguments passed to the `pause-recording` command */ + export type PauseRecording = {} + /** Arguments passed to the `resume-recording` command */ + export type ResumeRecording = {} + /** Arguments passed to the `toggle-pause` command */ + export type TogglePause = {} + /** Arguments passed to the `take-screenshot` command */ + export type TakeScreenshot = {} + /** Arguments passed to the `recording-status` command */ + export type RecordingStatus = {} + /** Arguments passed to the `open-settings` command */ + export type OpenSettings = {} +} + diff --git a/extensions/raycast/src/open-settings.tsx b/extensions/raycast/src/open-settings.tsx new file mode 100644 index 0000000000..fc3a7c05db --- /dev/null +++ b/extensions/raycast/src/open-settings.tsx @@ -0,0 +1,8 @@ +import { executeCapAction, createOpenSettingsAction } from "./utils"; + +export default async function Command() { + await executeCapAction(createOpenSettingsAction(), { + feedbackMessage: "Opening Cap settings...", + feedbackType: "hud", + }); +} diff --git a/extensions/raycast/src/pause-recording.tsx b/extensions/raycast/src/pause-recording.tsx new file mode 100644 index 0000000000..489450c670 --- /dev/null +++ b/extensions/raycast/src/pause-recording.tsx @@ -0,0 +1,8 @@ +import { executeCapAction, createPauseRecordingAction } from "./utils"; + +export default async function Command() { + await executeCapAction(createPauseRecordingAction(), { + feedbackMessage: "Pausing recording...", + feedbackType: "hud", + }); +} diff --git a/extensions/raycast/src/recording-status.tsx b/extensions/raycast/src/recording-status.tsx new file mode 100644 index 0000000000..5e47c56971 --- /dev/null +++ b/extensions/raycast/src/recording-status.tsx @@ -0,0 +1,8 @@ +import { executeCapAction, createGetStatusAction } from "./utils"; + +export default async function Command() { + await executeCapAction(createGetStatusAction(), { + feedbackMessage: "Checking recording status...", + feedbackType: "hud", + }); +} diff --git a/extensions/raycast/src/resume-recording.tsx b/extensions/raycast/src/resume-recording.tsx new file mode 100644 index 0000000000..ea861878b2 --- /dev/null +++ b/extensions/raycast/src/resume-recording.tsx @@ -0,0 +1,8 @@ +import { executeCapAction, createResumeRecordingAction } from "./utils"; + +export default async function Command() { + await executeCapAction(createResumeRecordingAction(), { + feedbackMessage: "Resuming recording...", + feedbackType: "hud", + }); +} diff --git a/extensions/raycast/src/start-recording.tsx b/extensions/raycast/src/start-recording.tsx new file mode 100644 index 0000000000..4e85b5d43d --- /dev/null +++ b/extensions/raycast/src/start-recording.tsx @@ -0,0 +1,56 @@ +import { Action, ActionPanel, Form, Icon } from "@raycast/api"; +import { useState } from "react"; +import { executeCapAction, createStartRecordingAction, capNotInstalled } from "./utils"; + +type CaptureType = "screen" | "window"; +type RecordingMode = "instant" | "studio"; + +export default function Command() { + const [captureType, setCaptureType] = useState("screen"); + const [targetName, setTargetName] = useState(""); + const [recordingMode, setRecordingMode] = useState("instant"); + + async function handleSubmit() { + if (await capNotInstalled()) { + return; + } + + if (!targetName.trim()) { + return; + } + + const captureMode = captureType === "screen" ? { screen: targetName } : { window: targetName }; + + await executeCapAction(createStartRecordingAction(captureMode, recordingMode), { + feedbackMessage: `Starting ${recordingMode} recording...`, + feedbackType: "hud", + }); + } + + return ( +
+ + + } + > + setCaptureType(v as CaptureType)}> + + + + + setRecordingMode(v as RecordingMode)}> + + + + + + ); +} diff --git a/extensions/raycast/src/stop-recording.tsx b/extensions/raycast/src/stop-recording.tsx new file mode 100644 index 0000000000..c8fcb84231 --- /dev/null +++ b/extensions/raycast/src/stop-recording.tsx @@ -0,0 +1,8 @@ +import { executeCapAction, createStopRecordingAction } from "./utils"; + +export default async function Command() { + await executeCapAction(createStopRecordingAction(), { + feedbackMessage: "Stopping recording...", + feedbackType: "hud", + }); +} diff --git a/extensions/raycast/src/take-screenshot.tsx b/extensions/raycast/src/take-screenshot.tsx new file mode 100644 index 0000000000..1310f42466 --- /dev/null +++ b/extensions/raycast/src/take-screenshot.tsx @@ -0,0 +1,50 @@ +import { Action, ActionPanel, Form, Icon } from "@raycast/api"; +import { useState } from "react"; +import { executeCapAction, createTakeScreenshotAction, capNotInstalled } from "./utils"; + +type CaptureType = "screen" | "window"; + +export default function Command() { + const [captureType, setCaptureType] = useState("screen"); + const [targetName, setTargetName] = useState(""); + + async function handleSubmit() { + if (await capNotInstalled()) { + return; + } + + if (!targetName.trim()) { + return; + } + + const captureMode = captureType === "screen" ? { screen: targetName } : { window: targetName }; + + await executeCapAction(createTakeScreenshotAction(captureMode), { + feedbackMessage: "Taking screenshot...", + feedbackType: "hud", + }); + } + + return ( +
+ + + } + > + setCaptureType(v as CaptureType)}> + + + + + + + ); +} diff --git a/extensions/raycast/src/toggle-pause.tsx b/extensions/raycast/src/toggle-pause.tsx new file mode 100644 index 0000000000..fb80ccaa8a --- /dev/null +++ b/extensions/raycast/src/toggle-pause.tsx @@ -0,0 +1,8 @@ +import { executeCapAction, createTogglePauseAction } from "./utils"; + +export default async function Command() { + await executeCapAction(createTogglePauseAction(), { + feedbackMessage: "Toggling pause...", + feedbackType: "hud", + }); +} diff --git a/extensions/raycast/src/utils.ts b/extensions/raycast/src/utils.ts new file mode 100644 index 0000000000..ada95f9df6 --- /dev/null +++ b/extensions/raycast/src/utils.ts @@ -0,0 +1,184 @@ +import { closeMainWindow, getApplications, Keyboard, open, showHUD, showToast, Toast } from "@raycast/api"; + +const CAP_BUNDLE_ID = "so.cap.desktop"; +const CAP_DEV_BUNDLE_ID = "so.cap.desktop.dev"; +const CAP_URL_SCHEME = "cap-desktop"; + +export interface DeepLinkAction { + [key: string]: unknown; +} + +export async function capNotInstalled(showErrorToast = true): Promise { + const apps = await getApplications(); + const installed = apps.some( + (app) => app.bundleId === CAP_BUNDLE_ID || app.bundleId === CAP_DEV_BUNDLE_ID + ); + + if (!installed && showErrorToast) { + showToast({ + style: Toast.Style.Failure, + title: "Cap is not installed!", + primaryAction: { + title: "Install Cap", + shortcut: Keyboard.Shortcut.Common.Open, + onAction: () => { + open("https://cap.so/download"); + }, + }, + }); + } + + return !installed; +} + +export async function executeCapAction( + action: DeepLinkAction, + options?: { + feedbackMessage?: string; + feedbackType?: "toast" | "hud"; + } +): Promise { + if (await capNotInstalled()) { + return false; + } + + const jsonValue = JSON.stringify(action); + const encodedValue = encodeURIComponent(jsonValue); + const url = `${CAP_URL_SCHEME}://action?value=${encodedValue}`; + + await closeMainWindow({ clearRootSearch: true }); + await open(url); + + if (options?.feedbackMessage) { + if (!options.feedbackType || options.feedbackType === "toast") { + showToast({ style: Toast.Style.Success, title: options.feedbackMessage }); + } else { + showHUD(options.feedbackMessage); + } + } + + return true; +} + +export interface RecordingStatus { + is_recording: boolean; + is_paused: boolean; + recording_mode: string | null; +} + +export interface DeepLinkCamera { + name: string; + id: string; +} + +export interface DeepLinkScreen { + name: string; + id: string; +} + +export interface DeepLinkWindow { + name: string; + owner_name: string; +} + +export interface DeepLinkDevices { + cameras: DeepLinkCamera[]; + microphones: string[]; + screens: DeepLinkScreen[]; + windows: DeepLinkWindow[]; +} + +export type RecordingMode = "instant" | "studio"; + +export interface CaptureMode { + screen?: string; + window?: string; +} + +export function createStartRecordingAction( + captureMode: CaptureMode, + mode: RecordingMode = "instant", + options?: { + camera?: { device: string } | { model: string } | null; + mic_label?: string | null; + capture_system_audio?: boolean; + } +): DeepLinkAction { + return { + start_recording: { + capture_mode: captureMode, + camera: options?.camera ?? null, + mic_label: options?.mic_label ?? null, + capture_system_audio: options?.capture_system_audio ?? false, + mode, + }, + }; +} + +export function createStopRecordingAction(): DeepLinkAction { + return { stop_recording: {} }; +} + +export function createPauseRecordingAction(): DeepLinkAction { + return { pause_recording: {} }; +} + +export function createResumeRecordingAction(): DeepLinkAction { + return { resume_recording: {} }; +} + +export function createTogglePauseAction(): DeepLinkAction { + return { toggle_pause_recording: {} }; +} + +export function createRestartRecordingAction(): DeepLinkAction { + return { restart_recording: {} }; +} + +export function createTakeScreenshotAction(captureMode: CaptureMode): DeepLinkAction { + return { + take_screenshot: { + capture_mode: captureMode, + }, + }; +} + +export function createSetMicrophoneAction(label: string | null): DeepLinkAction { + return { + set_microphone: { + label, + }, + }; +} + +export function createSetCameraAction(id: { device: string } | { model: string } | null): DeepLinkAction { + return { + set_camera: { + id, + }, + }; +} + +export function createListDevicesAction(): DeepLinkAction { + return { list_devices: {} }; +} + +export function createGetStatusAction(): DeepLinkAction { + return { get_status: {} }; +} + +export function createOpenSettingsAction(page?: string): DeepLinkAction { + return { + open_settings: { + page: page ?? null, + }, + }; +} + +export function createOpenEditorAction(projectPath: string): DeepLinkAction { + return { + open_editor: { + project_path: projectPath, + }, + }; +} diff --git a/extensions/raycast/tsconfig.json b/extensions/raycast/tsconfig.json new file mode 100644 index 0000000000..3be6953bd5 --- /dev/null +++ b/extensions/raycast/tsconfig.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Raycast Extension", + "compilerOptions": { + "lib": ["ES2023"], + "module": "ES2022", + "target": "ES2022", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "jsx": "react-jsx", + "noEmit": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true + }, + "include": ["src/**/*"] +} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 614993f77e..01dc46975d 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,7 @@ packages: - "apps/*" - "packages/*" + - "extensions/*" - "crates/tauri-plugin-*" - "infra" - "scripts/orgIdBackfill"