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,33 @@
{
"extends": [
"../../../../.eslintrc.base.json"
],
"ignorePatterns": [
"!**/*"
],
"overrides": [
{
"files": [
"*.ts",
"*.tsx",
"*.js",
"*.jsx"
],
"rules": {}
},
{
"files": [
"*.ts",
"*.tsx"
],
"rules": {}
},
{
"files": [
"*.js",
"*.jsx"
],
"rules": {}
}
]
}

View File

@@ -0,0 +1,7 @@
# pieces-openmic-ai
This library was generated with [Nx](https://nx.dev).
## Building
Run `nx build pieces-openmic-ai` to build the library.

View File

@@ -0,0 +1,10 @@
{
"name": "@activepieces/piece-openmic-ai",
"version": "0.0.1",
"type": "commonjs",
"main": "./src/index.js",
"types": "./src/index.d.ts",
"dependencies": {
"tslib": "^2.3.0"
}
}

View File

@@ -0,0 +1,65 @@
{
"name": "pieces-openmic-ai",
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/pieces/community/openmic-ai/src",
"projectType": "library",
"release": {
"version": {
"manifestRootsToUpdate": [
"dist/{projectRoot}"
],
"currentVersionResolver": "git-tag",
"fallbackCurrentVersionResolver": "disk"
}
},
"tags": [],
"targets": {
"build": {
"executor": "@nx/js:tsc",
"outputs": [
"{options.outputPath}"
],
"options": {
"outputPath": "dist/packages/pieces/community/openmic-ai",
"tsConfig": "packages/pieces/community/openmic-ai/tsconfig.lib.json",
"packageJson": "packages/pieces/community/openmic-ai/package.json",
"main": "packages/pieces/community/openmic-ai/src/index.ts",
"assets": [
"packages/pieces/community/openmic-ai/*.md",
{
"input": "packages/pieces/community/openmic-ai/src/i18n",
"output": "./src/i18n",
"glob": "**/!(i18n.json)"
}
],
"buildableProjectDepsInPackageJsonType": "dependencies",
"updateBuildableProjectDepsInPackageJson": true
},
"dependsOn": [
"prebuild",
"^build"
]
},
"nx-release-publish": {
"options": {
"packageRoot": "dist/{projectRoot}"
}
},
"prebuild": {
"dependsOn": [
"^build"
],
"executor": "nx:run-commands",
"options": {
"cwd": "packages/pieces/community/openmic-ai",
"command": "bun install --no-save --silent"
}
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": [
"{options.outputFile}"
]
}
}
}

View File

@@ -0,0 +1,82 @@
{
"An AI-powered platform that automates phone calls using advanced language models.": "An AI-powered platform that automates phone calls using advanced language models.",
"\nTo get your Openmic AI API key:\n1. Log in to your Openmic dashboard at https://app.openmic.ai\n2. Navigate to API Keys section in the left Navigation\n3. Generate a new API key\n4. Copy and paste the key here\n ": "\nTo get your Openmic AI API key:\n1. Log in to your Openmic dashboard at https://app.openmic.ai\n2. Navigate to API Keys section in the left Navigation\n3. Generate a new API key\n4. Copy and paste the key here\n ",
"Create Phone Call": "Create Phone Call",
"Find Bot": "Find Bot",
"Find Call": "Find Call",
"Get Bots": "Get Bots",
"Get Calls": "Get Calls",
"Custom API Call": "Custom API Call",
"Create a new outbound phone call using OpenMic AI": "Create a new outbound phone call using OpenMic AI",
"Retrieve details of a specific bot by its UID": "Retrieve details of a specific bot by its UID",
"Retrieve details of a specific call by its ID": "Retrieve details of a specific call by its ID",
"Retrieve all bots with optional filtering and pagination": "Retrieve all bots with optional filtering and pagination",
"Retrieve all calls with optional filtering and pagination": "Retrieve all calls with optional filtering and pagination",
"Make a custom API call to a specific endpoint": "Make a custom API call to a specific endpoint",
"From Number": "From Number",
"To Number": "To Number",
"Agent ID": "Agent ID",
"Customer ID": "Customer ID",
"Dynamic Variables": "Dynamic Variables",
"Callback URL": "Callback URL",
"Bot UID": "Bot UID",
"Call ID": "Call ID",
"Bot Name": "Bot Name",
"Created After": "Created After",
"Created Before": "Created Before",
"Limit": "Limit",
"Offset": "Offset",
"Bot ID": "Bot ID",
"From Date": "From Date",
"To Date": "To Date",
"Call Status": "Call Status",
"Call Type": "Call Type",
"Method": "Method",
"Headers": "Headers",
"Query Parameters": "Query Parameters",
"Body": "Body",
"Response is Binary ?": "Response is Binary ?",
"No Error on Failure": "No Error on Failure",
"Timeout (in seconds)": "Timeout (in seconds)",
"The number you own in E.164 format (e.g., +1234567890)": "The number you own in E.164 format (e.g., +1234567890)",
"The number you want to call in E.164 format (e.g., +0987654321)": "The number you want to call in E.164 format (e.g., +0987654321)",
"The bot UID to override the default agent ": "The bot UID to override the default agent ",
"Customer identifier for tracking ": "Customer identifier for tracking ",
"Key-value pairs to replace in the prompt": "Key-value pairs to replace in the prompt",
"Callback URL to receive call events ": "Callback URL to receive call events ",
"The unique identifier of the bot": "The unique identifier of the bot",
"The unique identifier of the call": "The unique identifier of the call",
"Filter by bot name (partial match)": "Filter by bot name (partial match)",
"Filter bots created after this date (ISO 8601 format)": "Filter bots created after this date (ISO 8601 format)",
"Filter bots created before this date (ISO 8601 format)": "Filter bots created before this date (ISO 8601 format)",
"Maximum number of bots to return (1-100)": "Maximum number of bots to return (1-100)",
"Number of bots to skip for pagination": "Number of bots to skip for pagination",
"Filter by customer ID": "Filter by customer ID",
"Filter by originating phone number": "Filter by originating phone number",
"Filter by destination phone number": "Filter by destination phone number",
"Filter by bot ID": "Filter by bot ID",
"Filter calls from this date (ISO 8601 format)": "Filter calls from this date (ISO 8601 format)",
"Filter calls to this date (ISO 8601 format)": "Filter calls to this date (ISO 8601 format)",
"Filter by call status": "Filter by call status",
"Filter by call type": "Filter by call type",
"Maximum number of calls to return (1-100)": "Maximum number of calls to return (1-100)",
"Number of calls to skip for pagination": "Number of calls to skip for pagination",
"Authorization headers are injected automatically from your connection.": "Authorization headers are injected automatically from your connection.",
"Enable for files like PDFs, images, etc..": "Enable for files like PDFs, images, etc..",
"Registered": "Registered",
"Ongoing": "Ongoing",
"Ended": "Ended",
"Error": "Error",
"Phone Call": "Phone Call",
"Web Call": "Web Call",
"GET": "GET",
"POST": "POST",
"PATCH": "PATCH",
"PUT": "PUT",
"DELETE": "DELETE",
"HEAD": "HEAD",
"New Post-call Summary": "New Post-call Summary",
"Trigger when a new call completes with comprehensive call data and analysis": "Trigger when a new call completes with comprehensive call data and analysis",
"Markdown": "Markdown",
"\n\n\n## Post-call Webhook Configuration\n\n### Steps to Configure in OpenMic AI:\n1. Open your bot in OpenMic AI\n2. Click on the **Post-Call** tab\n3. Go to the **Webhook** section\n4. In the **Post-Call Webhook URL** field, paste the URL below:\n\n```\n{{webhookUrl}}\n```\n\n5. Save your configuration\n\n[Learn more about post-call webhooks](https://docs.openmic.ai/post-call-webhook)\n": "\n\n\n## Post-call Webhook Configuration\n\n### Steps to Configure in OpenMic AI:\n1. Open your bot in OpenMic AI\n2. Click on the **Post-Call** tab\n3. Go to the **Webhook** section\n4. In the **Post-Call Webhook URL** field, paste the URL below:\n\n```\n{{webhookUrl}}\n```\n\n5. Save your configuration\n\n[Learn more about post-call webhooks](https://docs.openmic.ai/post-call-webhook)\n"
}

View File

@@ -0,0 +1,39 @@
import { createPiece } from '@activepieces/pieces-framework';
import { openmicAiAuth } from './lib/common/auth';
import { createPhoneCall } from './lib/actions/create-phone-call';
import { findBot } from './lib/actions/find-bot';
import { findCall } from './lib/actions/find-call';
import { getBots } from './lib/actions/get-bots';
import { getCalls } from './lib/actions/get-calls';
import { createCustomApiCallAction } from '@activepieces/pieces-common';
import { newPostCallSummary } from './lib/triggers/new-post-call-summary';
import { PieceCategory } from '@activepieces/shared';
import { BASE_URL } from './lib/common/client';
export const openmicAi = createPiece({
displayName: 'OpenMic AI',
auth: openmicAiAuth,
minimumSupportedRelease: '0.36.1',
logoUrl: 'https://cdn.activepieces.com/pieces/openmic-ai.png',
authors: ['sanket-a11y'],
description:
'An AI-powered platform that automates phone calls using advanced language models.',
categories: [PieceCategory.COMMUNICATION],
actions: [
createPhoneCall,
findBot,
findCall,
getBots,
getCalls,
createCustomApiCallAction({
auth: openmicAiAuth,
baseUrl: () => BASE_URL,
authMapping: async (auth) => {
return {
Authorization: `Bearer ${auth.secret_text}`,
};
},
}),
],
triggers: [newPostCallSummary],
});

View File

@@ -0,0 +1,75 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { openmicAiAuth } from '../common/auth';
import { HttpMethod } from '@activepieces/pieces-common';
import { makeRequest } from '../common/client';
export const createPhoneCall = createAction({
auth: openmicAiAuth,
name: 'createPhoneCall',
displayName: 'Create Phone Call',
description: 'Create a new outbound phone call using OpenMic AI',
props: {
fromNumber: Property.ShortText({
displayName: 'From Number',
description: 'The number you own in E.164 format (e.g., +1234567890)',
required: true,
}),
toNumber: Property.ShortText({
displayName: 'To Number',
description:
'The number you want to call in E.164 format (e.g., +0987654321)',
required: true,
}),
overrideAgentId: Property.ShortText({
displayName: 'Agent ID',
description: 'The bot UID to override the default agent ',
required: false,
}),
customerId: Property.ShortText({
displayName: 'Customer ID',
description: 'Customer identifier for tracking ',
required: false,
}),
dynamicVariables: Property.Object({
displayName: 'Dynamic Variables',
description: 'Key-value pairs to replace in the prompt',
required: false,
}),
callbackUrl: Property.ShortText({
displayName: 'Callback URL',
description: 'Callback URL to receive call events ',
required: false,
}),
},
async run(context) {
const body: Record<string, unknown> = {
from_number: context.propsValue.fromNumber,
to_number: context.propsValue.toNumber,
};
if (context.propsValue.overrideAgentId) {
body['override_agent_id'] = context.propsValue.overrideAgentId;
}
if (context.propsValue.customerId) {
body['customer_id'] = context.propsValue.customerId;
}
if (context.propsValue.dynamicVariables) {
body['dynamic_variables'] = context.propsValue.dynamicVariables;
}
if (context.propsValue.callbackUrl) {
body['callback_url'] = context.propsValue.callbackUrl;
}
const response = await makeRequest(
context.auth,
HttpMethod.POST,
'/create-phone-call',
body
);
return response.body;
},
});

View File

@@ -0,0 +1,27 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { openmicAiAuth } from '../common/auth';
import { HttpMethod } from '@activepieces/pieces-common';
import { makeRequest } from '../common/client';
export const findBot = createAction({
auth: openmicAiAuth,
name: 'findBot',
displayName: 'Find Bot',
description: 'Retrieve details of a specific bot by its UID',
props: {
uid: Property.ShortText({
displayName: 'Bot UID',
description: 'The unique identifier of the bot',
required: true,
}),
},
async run(context) {
const response = await makeRequest(
context.auth,
HttpMethod.GET,
`/bots/${context.propsValue.uid}`
);
return response.body;
},
});

View File

@@ -0,0 +1,27 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { openmicAiAuth } from '../common/auth';
import { HttpMethod } from '@activepieces/pieces-common';
import { makeRequest } from '../common/client';
export const findCall = createAction({
auth: openmicAiAuth,
name: 'findCall',
displayName: 'Find Call',
description: 'Retrieve details of a specific call by its ID',
props: {
callId: Property.ShortText({
displayName: 'Call ID',
description: 'The unique identifier of the call',
required: true,
}),
},
async run(context) {
const response = await makeRequest(
context.auth,
HttpMethod.GET,
`/call/${context.propsValue.callId}`
);
return response;
},
});

View File

@@ -0,0 +1,70 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { openmicAiAuth } from '../common/auth';
import { HttpMethod } from '@activepieces/pieces-common';
import { makeRequest } from '../common/client';
export const getBots = createAction({
auth: openmicAiAuth,
name: 'getBots',
displayName: 'Get Bots',
description: 'Retrieve all bots with optional filtering and pagination',
props: {
name: Property.ShortText({
displayName: 'Bot Name',
description: 'Filter by bot name (partial match)',
required: false,
}),
createdAfter: Property.ShortText({
displayName: 'Created After',
description: 'Filter bots created after this date (ISO 8601 format)',
required: false,
}),
createdBefore: Property.ShortText({
displayName: 'Created Before',
description: 'Filter bots created before this date (ISO 8601 format)',
required: false,
}),
limit: Property.Number({
displayName: 'Limit',
description: 'Maximum number of bots to return (1-100)',
required: false,
defaultValue: 10,
}),
offset: Property.Number({
displayName: 'Offset',
description: 'Number of bots to skip for pagination',
required: false,
defaultValue: 0,
}),
},
async run(context) {
const params = new URLSearchParams();
if (context.propsValue.limit) {
params.append('limit', String(context.propsValue.limit));
}
if (context.propsValue.offset !== undefined) {
params.append('offset', String(context.propsValue.offset));
}
if (context.propsValue.name) {
params.append('name', context.propsValue.name);
}
if (context.propsValue.createdAfter) {
params.append('created_after', context.propsValue.createdAfter);
}
if (context.propsValue.createdBefore) {
params.append('created_before', context.propsValue.createdBefore);
}
const queryString = params.toString();
const endpoint = queryString ? `/bots?${queryString}` : '/bots';
const response = await makeRequest(context.auth, HttpMethod.GET, endpoint);
return response;
},
});

View File

@@ -0,0 +1,131 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { openmicAiAuth } from '../common/auth';
import { HttpMethod } from '@activepieces/pieces-common';
import { makeRequest } from '../common/client';
export const getCalls = createAction({
auth: openmicAiAuth,
name: 'getCalls',
displayName: 'Get Calls',
description: 'Retrieve all calls with optional filtering and pagination',
props: {
customerId: Property.ShortText({
displayName: 'Customer ID',
description: 'Filter by customer ID',
required: false,
}),
fromNumber: Property.ShortText({
displayName: 'From Number',
description: 'Filter by originating phone number',
required: false,
}),
toNumber: Property.ShortText({
displayName: 'To Number',
description: 'Filter by destination phone number',
required: false,
}),
botId: Property.ShortText({
displayName: 'Bot ID',
description: 'Filter by bot ID',
required: false,
}),
fromDate: Property.ShortText({
displayName: 'From Date',
description: 'Filter calls from this date (ISO 8601 format)',
required: false,
}),
toDate: Property.ShortText({
displayName: 'To Date',
description: 'Filter calls to this date (ISO 8601 format)',
required: false,
}),
callStatus: Property.StaticDropdown({
displayName: 'Call Status',
description: 'Filter by call status',
required: false,
options: {
disabled: false,
options: [
{ label: 'Registered', value: 'registered' },
{ label: 'Ongoing', value: 'ongoing' },
{ label: 'Ended', value: 'ended' },
{ label: 'Error', value: 'error' },
],
},
}),
callType: Property.StaticDropdown({
displayName: 'Call Type',
description: 'Filter by call type',
required: false,
options: {
disabled: false,
options: [
{ label: 'Phone Call', value: 'phonecall' },
{ label: 'Web Call', value: 'webcall' },
],
},
}),
limit: Property.Number({
displayName: 'Limit',
description: 'Maximum number of calls to return (1-100)',
required: false,
defaultValue: 10,
}),
offset: Property.Number({
displayName: 'Offset',
description: 'Number of calls to skip for pagination',
required: false,
defaultValue: 0,
}),
},
async run(context) {
const params = new URLSearchParams();
if (context.propsValue.limit) {
params.append('limit', String(context.propsValue.limit));
}
if (context.propsValue.offset !== undefined) {
params.append('offset', String(context.propsValue.offset));
}
if (context.propsValue.customerId) {
params.append('customer_id', context.propsValue.customerId);
}
if (context.propsValue.fromNumber) {
params.append('from_number', context.propsValue.fromNumber);
}
if (context.propsValue.toNumber) {
params.append('to_number', context.propsValue.toNumber);
}
if (context.propsValue.botId) {
params.append('bot_id', context.propsValue.botId);
}
if (context.propsValue.fromDate) {
params.append('from_date', context.propsValue.fromDate);
}
if (context.propsValue.toDate) {
params.append('to_date', context.propsValue.toDate);
}
if (context.propsValue.callStatus) {
params.append('call_status', context.propsValue.callStatus);
}
if (context.propsValue.callType) {
params.append('call_type', context.propsValue.callType);
}
const queryString = params.toString();
const endpoint = queryString ? `/calls?${queryString}` : '/calls';
const response = await makeRequest(context.auth, HttpMethod.GET, endpoint);
return response;
},
});

View File

@@ -0,0 +1,38 @@
import { PieceAuth } from '@activepieces/pieces-framework';
import { makeRequest } from './client';
import { HttpMethod } from '@activepieces/pieces-common';
import { AppConnectionType } from '@activepieces/shared';
export const openmicAiAuth = PieceAuth.SecretText({
displayName: 'Openmic AI API Key',
description: `
To get your Openmic AI API key:
1. Log in to your Openmic dashboard at https://app.openmic.ai
2. Navigate to API Keys section in the left Navigation
3. Generate a new API key
4. Copy and paste the key here
`,
required: true,
validate: async ({ auth }) => {
if (auth) {
try {
await makeRequest({
secret_text: auth,
type: AppConnectionType.SECRET_TEXT,
}, HttpMethod.GET, '/calls');
return {
valid: true,
};
} catch (error) {
return {
valid: false,
error: 'Invalid Api Key',
};
}
}
return {
valid: false,
error: 'Invalid Api Key',
};
},
});

View File

@@ -0,0 +1,27 @@
import { HttpMethod, httpClient } from '@activepieces/pieces-common';
import { AppConnectionValueForAuthProperty } from '@activepieces/pieces-framework';
import { openmicAiAuth } from './auth';
export const BASE_URL = `https://api.openmic.ai/v1`;
export async function makeRequest(
api_key: AppConnectionValueForAuthProperty<typeof openmicAiAuth>,
method: HttpMethod,
path: string,
body?: unknown
) {
try {
const response = await httpClient.sendRequest({
method,
url: `${BASE_URL}${path}`,
headers: {
Authorization: `Bearer ${api_key.secret_text}`,
'Content-Type': 'application/json',
},
body,
});
return response.body;
} catch (error: any) {
throw new Error(`Unexpected error: ${error.message || String(error)}`);
}
}

View File

@@ -0,0 +1,88 @@
import {
createTrigger,
Property,
TriggerStrategy,
} from '@activepieces/pieces-framework';
import { openmicAiAuth } from '../common/auth';
import { MarkdownVariant } from '@activepieces/shared';
const webhookConfigDescription = `
## Post-call Webhook Configuration
### Steps to Configure in OpenMic AI:
1. Open your bot in OpenMic AI
2. Click on the **Post-Call** tab
3. Go to the **Webhook** section
4. In the **Post-Call Webhook URL** field, paste the URL below:
\`\`\`
{{webhookUrl}}
\`\`\`
5. Save your configuration
[Learn more about post-call webhooks](https://docs.openmic.ai/post-call-webhook)
`;
export const newPostCallSummary = createTrigger({
auth: openmicAiAuth,
name: 'newPostCallSummary',
displayName: 'New Post-call Summary',
description:
'Trigger when a new call completes with comprehensive call data and analysis',
props: {
markdown: Property.MarkDown({
variant: MarkdownVariant.INFO,
value: `
${webhookConfigDescription}`,
}),
},
sampleData: {
type: 'end-of-call-report',
sessionId: 'cmdx5w8oc0005q671s3cbg063',
toPhoneNumber: '+916297653534',
fromPhoneNumber: '+16167948654',
callDuration: '0:00:45.723362',
callType: 'phonecall',
disconnectionReason: 'user_ended_call',
direction: 'outbound',
createdAt: '2025-08-04T13:44:26.604Z',
endedAt: '2025-08-04T13:45:03.325Z',
callPickup: 'yes',
sessionType: 'voice',
transcript: [
['assistant', 'Hello I am Jay.'],
['user', 'Yeah. Hi.'],
[
'assistant',
'Hey Soumyadip! Great to hear from you. How can I help you today? Want me to reserve some raisins for you?',
],
['user', 'Gotcha.'],
[
'assistant',
"Awesome! I'll go ahead and reserve a pack of raisins for you at Shri Balaji Traders. Just let me know when you want to pick them up or if you'd like me to help with an online order. Cheers!",
],
],
summary:
'Jay greeted Soumyadip and offered to reserve a pack of raisins at Shri Balaji Traders. The conversation focused on confirming the reservation, with options for pickup or online order.',
isSuccessful: false,
successEvaluation: true,
extractedData: {
product: 'Raisins',
},
dynamicVariables: {
customerName: 'Soumyadip Moni',
},
},
type: TriggerStrategy.WEBHOOK,
async onEnable() {
// implement webhook creation logic
},
async onDisable() {
// implement webhook deletion logic
},
async run(context) {
return [context.payload.body];
},
});

View File

@@ -0,0 +1,20 @@
{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"module": "commonjs",
"forceConsistentCasingInFileNames": true,
"strict": true,
"importHelpers": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noPropertyAccessFromIndexSignature": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}

View File

@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../../dist/out-tsc",
"declaration": true,
"types": ["node"]
},
"include": ["src/**/*.ts"]
}