Add Activepieces integration for workflow automation

- Add Activepieces fork with SmoothSchedule custom piece
- Create integrations app with Activepieces service layer
- Add embed token endpoint for iframe integration
- Create Automations page with embedded workflow builder
- Add sidebar visibility fix for embed mode
- Add list inactive customers endpoint to Public API
- Include SmoothSchedule triggers: event created/updated/cancelled
- Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-18 22:59:37 -05:00
parent 9848268d34
commit 3aa7199503
16292 changed files with 1284892 additions and 4708 deletions

View File

@@ -0,0 +1,35 @@
import { PieceAuth } from "@activepieces/pieces-framework";
import { makeRequest } from "./client";
import { HttpMethod } from "@activepieces/pieces-common";
export const murfAuth = PieceAuth.SecretText({
displayName: "API Key",
description: "Enter your API key from https://murf.ai",
required: true,
validate: async ({ auth }) => {
if (!auth) {
return {
valid: false,
error: "API Key is required",
};
}
try {
const response = await makeRequest(auth as string, HttpMethod.GET, "/auth/token");
if (response && response.token) {
return { valid: true };
}
return {
valid: false,
error: "Invalid API key or token could not be generated",
};
} catch (e: any) {
return {
valid: false,
error: `Auth validation failed: ${e.message}`,
};
}
},
});

View File

@@ -0,0 +1,38 @@
import { HttpMethod, httpClient } from "@activepieces/pieces-common";
export const BASE_URL = "https://api.murf.ai/v1";
export async function makeRequest(
apiKey: string,
method: HttpMethod,
path: string,
body?: any,
isFormData = false
) {
try {
let headers: Record<string, string> = {
"api-key": apiKey,
};
const requestBody = body;
if (!isFormData) {
headers["Content-Type"] = "application/json";
} else if (body && typeof (body as any).getHeaders === "function") {
headers = { ...headers, ...(body as any).getHeaders() };
}
const response = await httpClient.sendRequest({
method,
url: `${BASE_URL}${path}`,
headers,
body: requestBody,
});
return response.body;
} catch (error: any) {
throw new Error(
`Unexpected error: ${JSON.stringify(error.response || error.message || error)}`
);
}
}

View File

@@ -0,0 +1,121 @@
import { Property } from "@activepieces/pieces-framework";
import { HttpMethod } from "@activepieces/pieces-common";
import { makeRequest } from "./client";
import { murfAuth } from "./auth";
// Helper to fetch voices
const getVoices = async (apiKey: string) => {
return await makeRequest(apiKey, HttpMethod.GET, "/speech/voices");
};
// Helper to build unique language list
const getLanguages = async (apiKey: string) => {
const voices = await getVoices(apiKey);
const languageMap = new Map<string, string>();
voices.forEach((voice: any) => {
if (voice.supportedLocales) {
Object.keys(voice.supportedLocales).forEach((localeCode) => {
if (!languageMap.has(localeCode)) {
languageMap.set(
localeCode,
voice.supportedLocales[localeCode].detail || localeCode
);
}
});
}
});
return Array.from(languageMap, ([value, label]) => ({ label, value }));
};
export const murfCommon = {
language: Property.Dropdown({
auth: murfAuth,
displayName: "Language",
description: "Select your preferred language for the translated output.",
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: "Please connect your Murf account first.",
options: [],
};
}
const langs = await getLanguages(auth.secret_text);
return {
disabled: false,
options: langs,
};
},
}),
voiceId: Property.Dropdown({
auth: murfAuth,
displayName: "Voice",
description: "Choose a voice for converting text into speech",
required: true,
refreshers: ["language"],
options: async ({ auth, language }) => {
if (!auth|| !language) {
return {
disabled: true,
placeholder: "Please select a language and connect your Murf account first.",
options: [],
};
}
const voices = await getVoices(auth.secret_text);
const filtered = voices.filter((v: any) =>
Object.keys(v.supportedLocales || {}).includes(language as string)
);
return {
disabled: false,
options: filtered.map((v: any) => ({
label: `${v.displayName} (${v.gender}, ${v.locale})`,
value: v.voiceId,
})),
};
},
}),
sourceLocale: Property.Dropdown({
auth: murfAuth,
displayName: "Source Locale",
description: "Select the source locale for input text.",
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: "Please connect your Murf account first.",
options: [],
};
}
const voices = await getVoices(auth.secret_text);
const localeMap = new Map<string, string>();
voices.forEach((voice: any) => {
if (voice.supportedLocales) {
Object.entries(voice.supportedLocales).forEach(([localeCode, localeData]: any) => {
if (!localeMap.has(localeCode)) {
localeMap.set(localeCode, localeData.detail);
}
});
}
});
return {
disabled: false,
options: Array.from(localeMap, ([value, label]) => ({ value, label })),
};
},
}),
};