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:
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"extends": [
|
||||
"../../../../.eslintrc.base.json"
|
||||
],
|
||||
"ignorePatterns": [
|
||||
"!**/*"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"*.ts",
|
||||
"*.tsx",
|
||||
"*.js",
|
||||
"*.jsx"
|
||||
],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"*.ts",
|
||||
"*.tsx"
|
||||
],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"*.js",
|
||||
"*.jsx"
|
||||
],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
# pieces-fellow
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Building
|
||||
|
||||
Run `nx build pieces-fellow` to build the library.
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "@activepieces/piece-fellow",
|
||||
"version": "0.0.1",
|
||||
"type": "commonjs",
|
||||
"main": "./src/index.js",
|
||||
"types": "./src/index.d.ts",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"name": "pieces-fellow",
|
||||
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "packages/pieces/community/fellow/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/fellow",
|
||||
"tsConfig": "packages/pieces/community/fellow/tsconfig.lib.json",
|
||||
"packageJson": "packages/pieces/community/fellow/package.json",
|
||||
"main": "packages/pieces/community/fellow/src/index.ts",
|
||||
"assets": [
|
||||
"packages/pieces/community/fellow/*.md",
|
||||
{
|
||||
"input": "packages/pieces/community/fellow/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/fellow",
|
||||
"command": "bun install --no-save --silent"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": [
|
||||
"{options.outputFile}"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { createPiece } from '@activepieces/pieces-framework';
|
||||
import { fellowAuth, getBaseUrl } from './lib/common/auth';
|
||||
import { PieceCategory } from '@activepieces/shared';
|
||||
import { getNoteAction } from './lib/actions/get-note';
|
||||
import { newRecordingTrigger } from './lib/triggers/new-recording';
|
||||
import { createCustomApiCallAction } from '@activepieces/pieces-common';
|
||||
|
||||
export const fellow = createPiece({
|
||||
displayName: 'Fellow.ai',
|
||||
description: 'AI Meeting Assistant and Notetaker',
|
||||
categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE, PieceCategory.PRODUCTIVITY],
|
||||
auth: fellowAuth,
|
||||
minimumSupportedRelease: '0.36.1',
|
||||
logoUrl: 'https://cdn.activepieces.com/pieces/fellow.png',
|
||||
authors: ['kishanprmr'],
|
||||
actions: [getNoteAction,
|
||||
createCustomApiCallAction({
|
||||
auth: fellowAuth,
|
||||
baseUrl: (auth) => {
|
||||
return getBaseUrl(auth?.props.subdomain ?? '')
|
||||
},
|
||||
authMapping: async (auth) => {
|
||||
return {
|
||||
'X-API-KEY': `${auth.props.apiKey}`
|
||||
}
|
||||
}
|
||||
})],
|
||||
triggers: [newRecordingTrigger],
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
import { createAction, Property } from "@activepieces/pieces-framework";
|
||||
import { fellowAuth, getBaseUrl } from "../common/auth";
|
||||
import { httpClient, HttpMethod } from "@activepieces/pieces-common";
|
||||
|
||||
export const getNoteAction = createAction({
|
||||
name: 'get-note',
|
||||
auth: fellowAuth,
|
||||
displayName: 'Get AI Note',
|
||||
description: 'Retrieves a note by its ID.',
|
||||
props: {
|
||||
noteId: Property.ShortText({
|
||||
displayName: 'Note ID',
|
||||
required: true
|
||||
})
|
||||
},
|
||||
async run(context) {
|
||||
const { subdomain, apiKey } = context.auth.props;
|
||||
const { noteId } = context.propsValue;
|
||||
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: getBaseUrl(subdomain) + `/note/${noteId}`,
|
||||
headers: {
|
||||
'X-API-KEY': apiKey
|
||||
}
|
||||
})
|
||||
|
||||
return response.body;
|
||||
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,43 @@
|
||||
import { PieceAuth, Property } from '@activepieces/pieces-framework';
|
||||
import { httpClient, HttpMethod } from "@activepieces/pieces-common";
|
||||
|
||||
export const fellowAuth = PieceAuth.CustomAuth({
|
||||
required: true,
|
||||
props: {
|
||||
apiKey: Property.ShortText({
|
||||
displayName: 'API Key',
|
||||
description: `You can obtain API key by navigating to **User Settings -> Developer Tools**.`,
|
||||
required: true,
|
||||
}),
|
||||
subdomain: Property.ShortText({
|
||||
displayName: 'Subdomain',
|
||||
description: `You can obtain your workspace domain from URL.For example,subdomain for 'https://**test**.fellow.app/' is **test**.`,
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
validate: async ({ auth }) => {
|
||||
try {
|
||||
await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: getBaseUrl(auth.subdomain) + '/me',
|
||||
headers: {
|
||||
'X-API-KEY': auth.apiKey
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
valid: true
|
||||
}
|
||||
}
|
||||
catch {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'Invalid Credentials.'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const getBaseUrl = (subdomain: string) => {
|
||||
return `https://${subdomain}.fellow.app/api/v1`;
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
export type ListRecordingsResponse = {
|
||||
recordings:{
|
||||
page_info:{
|
||||
cursor:string|null;
|
||||
page_size:number;
|
||||
},
|
||||
data:{
|
||||
id:string,
|
||||
started_at:string
|
||||
}[];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import { AppConnectionValueForAuthProperty, createTrigger, TriggerStrategy } from "@activepieces/pieces-framework";
|
||||
import { fellowAuth, getBaseUrl } from "../common/auth";
|
||||
import { DedupeStrategy, httpClient, HttpMethod, Polling, pollingHelper } from "@activepieces/pieces-common";
|
||||
import dayjs from 'dayjs';
|
||||
import { ListRecordingsResponse } from "../common/types";
|
||||
import { isNil } from "@activepieces/shared";
|
||||
|
||||
const polling: Polling<AppConnectionValueForAuthProperty<typeof fellowAuth>, Record<string, never>> = {
|
||||
strategy: DedupeStrategy.TIMEBASED,
|
||||
async items({ auth, lastFetchEpochMS }) {
|
||||
const { subdomain, apiKey } = auth.props;
|
||||
const isTestMode = lastFetchEpochMS === 0;
|
||||
|
||||
let hasMore = true;
|
||||
let cursor: string | null = null;
|
||||
const recordings = [];
|
||||
|
||||
do {
|
||||
const requestBody: Record<string, any> = {
|
||||
pagination: {
|
||||
page_size: 20,
|
||||
cursor
|
||||
},
|
||||
include: {
|
||||
transcript: true,
|
||||
ai_notes: true
|
||||
},
|
||||
}
|
||||
|
||||
if (!isTestMode) {
|
||||
requestBody['filters'] = {
|
||||
created_at_start: dayjs(lastFetchEpochMS).toISOString()
|
||||
}
|
||||
}
|
||||
|
||||
const response = await httpClient.sendRequest<ListRecordingsResponse>({
|
||||
method: HttpMethod.POST,
|
||||
url: getBaseUrl(subdomain) + '/recordings',
|
||||
headers: {
|
||||
'X-API-KEY': apiKey
|
||||
},
|
||||
body: requestBody
|
||||
})
|
||||
|
||||
|
||||
for (const recording of response.body.recordings.data ?? []) {
|
||||
recordings.push(recording);
|
||||
}
|
||||
|
||||
if (isTestMode) break;
|
||||
|
||||
cursor = response.body.recordings.page_info.cursor;
|
||||
hasMore = !isNil(cursor);
|
||||
|
||||
} while (hasMore)
|
||||
|
||||
return recordings.map((rec) => ({
|
||||
epochMilliSeconds: dayjs(rec.started_at).valueOf(),
|
||||
data: rec
|
||||
}))
|
||||
},
|
||||
}
|
||||
|
||||
export const newRecordingTrigger = createTrigger({
|
||||
name: 'new-recording',
|
||||
auth: fellowAuth,
|
||||
displayName: 'New Recording',
|
||||
description: 'Triggers when a new recording is created.',
|
||||
type: TriggerStrategy.POLLING,
|
||||
props: {},
|
||||
async onEnable(context) {
|
||||
await pollingHelper.onEnable(polling, {
|
||||
auth: context.auth,
|
||||
store: context.store,
|
||||
propsValue: context.propsValue,
|
||||
});
|
||||
},
|
||||
async onDisable(context) {
|
||||
await pollingHelper.onDisable(polling, {
|
||||
auth: context.auth,
|
||||
store: context.store,
|
||||
propsValue: context.propsValue,
|
||||
});
|
||||
},
|
||||
async test(context) {
|
||||
return await pollingHelper.test(polling, context);
|
||||
},
|
||||
async run(context) {
|
||||
return await pollingHelper.poll(polling, context);
|
||||
},
|
||||
sampleData: undefined
|
||||
})
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../../dist/out-tsc",
|
||||
"declaration": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user