import { z, IntegrationDefinition } from '@botpress/sdk' export default new IntegrationDefinition({ name: 'canny', version: '0.2.3', title: 'Canny', description: 'Connect your Botpress bot to Canny for feature request management and customer feedback collection', readme: 'hub.md', icon: 'icon.svg', configuration: { schema: z.object({ apiKey: z.string().title('API Key').describe('Your Canny API key'), }), }, channels: { posts: { title: 'Posts', description: 'Channel for Canny posts and comments', messages: { text: { schema: z.object({ text: z.string(), }), }, image: { schema: z.object({ imageUrl: z.string(), caption: z.string().optional(), }), }, }, conversation: { tags: { cannyPostId: { title: 'Post ID', description: 'The Canny post ID', }, cannyBoardId: { title: 'Board ID', description: 'The Canny board ID', }, }, }, message: { tags: { cannyType: { title: 'Type', description: 'Whether this is a post or comment', }, cannyPostId: { title: 'Post ID', description: 'The associated post ID', }, cannyCommentId: { title: 'Comment ID', description: 'The comment ID (if applicable)', }, }, }, }, }, actions: { // Post actions createPost: { title: 'Create Post', description: 'Create a new post in Canny', input: { schema: z.object({ authorId: z .string() .optional() .title('Author Id') .describe('The author Id (defaults to Botpress user if not provided)'), boardId: z.string().title('Board Id').describe('The board Id'), title: z.string().title('Post Title').describe('Post title'), details: z.string().title('Post Details').describe('Post details'), byId: z.string().optional().title('By Id').describe('Admin Id who created the post'), categoryId: z.string().optional().title('Category Id').describe('Category Id'), ownerId: z.string().optional().title('Owner Id').describe('Owner Id'), imageURLs: z.array(z.string()).optional().title('Image URLs').describe('Image URLs'), eta: z.string().optional().title('ETA').describe('ETA (MM/YYYY)'), etaPublic: z.boolean().optional().title('ETA Public').describe('Make ETA public'), customFields: z.record(z.any()).optional().title('Custom Fields').describe('Custom fields'), }), }, output: { schema: z.object({ postId: z.string().title('Post ID').describe('The ID of the created post'), }), }, }, getPost: { title: 'Get Post', description: 'Retrieve a post by ID', input: { schema: z.object({ postId: z.string().title('Post Id').describe('The post Id'), boardId: z.string().optional().title('Board Id').describe('The board Id'), }), }, output: { schema: z.object({ post: z .object({ id: z.string().title('ID').describe('Post ID'), title: z.string().title('Title').describe('Post title'), details: z.string().optional().title('Details').describe('Post details'), authorName: z.string().optional().title('Author Name').describe('Name of the post author'), authorEmail: z.string().optional().title('Author Email').describe('Email of the post author'), boardName: z.string().title('Board Name').describe('Name of the board'), status: z.string().title('Status').describe('Post status'), score: z.number().title('Score').describe('Post score'), commentCount: z.number().title('Comment Count').describe('Number of comments'), created: z.string().title('Created').describe('Creation timestamp'), url: z.string().title('URL').describe('Post URL'), }) .title('Post') .describe('The retrieved post object'), }), }, }, listPosts: { title: 'List Posts', description: 'List posts with optional filters', input: { schema: z.object({ boardId: z.string().optional().title('Board Id').describe('Filter posts by board Id'), authorId: z.string().optional().title('Author Id').describe('Filter posts by author Id'), companyId: z.string().optional().title('Company Id').describe('Filter posts by company Id'), tagIds: z.array(z.string()).optional().title('Tag Ids').describe('Filter posts by tag Ids'), limit: z.number().optional().title('Limit').describe('Number of posts to return'), nextToken: z.number().optional().title('Next Token').describe('Token for pagination - skip this many posts'), search: z.string().optional().title('Search').describe('Search term to filter posts'), sort: z .enum(['newest', 'oldest', 'relevance', 'score', 'statusChanged', 'trending']) .optional() .title('Sort') .describe('Sort order for posts'), status: z.string().optional().title('Status').describe('Filter posts by status'), }), }, output: { schema: z.object({ posts: z .array( z.object({ id: z.string().title('ID').describe('Post ID'), title: z.string().title('Title').describe('Post title'), details: z.string().optional().title('Details').describe('Post details'), authorName: z.string().optional().title('Author Name').describe('Name of the post author'), authorEmail: z.string().optional().title('Author Email').describe('Email of the post author'), boardName: z.string().title('Board Name').describe('Name of the board'), status: z.string().title('Status').describe('Post status'), score: z.number().title('Score').describe('Post score'), commentCount: z.number().title('Comment Count').describe('Number of comments'), created: z.string().title('Created').describe('Creation timestamp'), url: z.string().title('URL').describe('Post URL'), }) ) .title('Posts') .describe('Array of posts'), nextToken: z.number().optional().title('Next Token').describe('Token for next page if more posts available'), }), }, }, updatePost: { title: 'Update Post', description: 'Update an existing post', input: { schema: z.object({ postId: z.string().title('Post Id').describe('The post Id'), title: z.string().optional().title('Title').describe('Updated post title'), details: z.string().optional().title('Details').describe('Updated post details'), eta: z.string().optional().title('ETA').describe('Updated ETA (MM/YYYY)'), etaPublic: z.boolean().optional().title('ETA Public').describe('Make ETA public'), imageURLs: z.array(z.string()).optional().title('Image URLs').describe('Updated image URLs'), customFields: z.record(z.any()).optional().title('Custom Fields').describe('Updated custom fields'), }), }, output: { schema: z.object({}), }, }, deletePost: { title: 'Delete Post', description: 'Delete a post', input: { schema: z.object({ postId: z.string().title('Post Id').describe('The post Id to delete'), }), }, output: { schema: z.object({}), }, }, // Comment actions createComment: { title: 'Create Comment', description: 'Create a new comment on a post', input: { schema: z.object({ authorId: z .string() .optional() .title('Author Id') .describe('The author Id (defaults to Botpress user if not provided)'), postId: z.string().title('Post Id').describe('The post Id'), value: z.string().title('Comment Text').describe('Comment text'), parentId: z.string().optional().title('Parent Id').describe('Parent comment Id for replies'), imageURLs: z.array(z.string()).optional().title('Image URLs').describe('Image URLs'), internal: z.boolean().optional().title('Internal').describe('Whether this is an internal comment'), shouldNotifyVoters: z.boolean().optional().title('Notify Voters').describe('Notify voters'), createdAt: z.string().optional().title('Created At').describe('Created timestamp'), }), }, output: { schema: z.object({ commentId: z.string().title('Comment ID').describe('The ID of the created comment'), }), }, }, getComment: { title: 'Get Comment', description: 'Retrieve a comment by ID', input: { schema: z.object({ commentId: z.string().title('Comment Id').describe('The comment Id'), }), }, output: { schema: z.object({ comment: z .object({ id: z.string().title('ID').describe('Comment ID'), value: z.string().title('Value').describe('Comment text'), authorName: z.string().title('Author Name').describe('Name of the comment author'), authorEmail: z.string().title('Author Email').describe('Email of the comment author'), postTitle: z.string().title('Post Title').describe('Title of the associated post'), postId: z.string().title('Post ID').describe('ID of the associated post'), created: z.string().title('Created').describe('Creation timestamp'), internal: z.boolean().title('Internal').describe('Whether this is an internal comment'), likeCount: z.number().title('Like Count').describe('Number of likes'), }) .title('Comment') .describe('The retrieved comment object'), }), }, }, listComments: { title: 'List Comments', description: 'List comments with optional filters', input: { schema: z.object({ postId: z.string().optional().title('Post Id').describe('Filter comments by post Id'), authorId: z.string().optional().title('Author Id').describe('Filter comments by author Id'), boardId: z.string().optional().title('Board Id').describe('Filter comments by board Id'), companyId: z.string().optional().title('Company Id').describe('Filter comments by company Id'), limit: z.number().optional().title('Limit').describe('Number of comments to return'), nextToken: z .number() .optional() .title('Next Token') .describe('Token for pagination - skip this many comments'), }), }, output: { schema: z.object({ comments: z .array( z.object({ id: z.string().title('ID').describe('Comment ID'), value: z.string().title('Value').describe('Comment text'), authorName: z.string().title('Author Name').describe('Name of the comment author'), authorEmail: z.string().title('Author Email').describe('Email of the comment author'), postTitle: z.string().title('Post Title').describe('Title of the associated post'), postId: z.string().title('Post ID').describe('ID of the associated post'), created: z.string().title('Created').describe('Creation timestamp'), internal: z.boolean().title('Internal').describe('Whether this is an internal comment'), likeCount: z.number().title('Like Count').describe('Number of likes'), }) ) .title('Comments') .describe('Array of comments'), nextToken: z .number() .optional() .title('Next Token') .describe('Token for next page if more comments available'), }), }, }, deleteComment: { title: 'Delete Comment', description: 'Delete a comment', input: { schema: z.object({ commentId: z.string().title('Comment Id').describe('The comment Id to delete'), }), }, output: { schema: z.object({}), }, }, // User management actions createOrUpdateUser: { title: 'Create or Update User', description: 'Create a new user or update an existing one in Canny', input: { schema: z.object({ name: z.string().min(1).max(50).title('Name').describe('User display name (required, 1-50 characters)'), userId: z.string().optional().title('User Id').describe('Your internal user Id'), email: z.string().optional().title('Email').describe('User email address'), avatarURL: z.string().optional().title('Avatar URL').describe('User avatar URL'), alias: z.string().optional().title('Alias').describe('User alias'), created: z.string().optional().title('Created').describe('User creation timestamp'), customFields: z.record(z.any()).optional().title('Custom Fields').describe('Custom fields'), }), }, output: { schema: z.object({ user: z .object({ id: z.string().title('ID').describe('User ID'), name: z.string().title('Name').describe('User display name'), email: z.string().title('Email').describe('User email address'), avatarURL: z.string().optional().title('Avatar URL').describe('User avatar URL'), userId: z.string().title('User ID').describe('Your internal user ID'), isAdmin: z.boolean().title('Is Admin').describe('Whether the user is an admin'), created: z.string().title('Created').describe('User creation timestamp'), }) .title('User') .describe('The created or updated user object'), }), }, }, listUsers: { title: 'List Users', description: 'List all users in your Canny workspace with pagination', input: { schema: z.object({ limit: z .number() .min(1) .max(100) .optional() .title('Limit') .describe('Number of users to fetch (1-100, defaults to 10)'), nextToken: z.string().optional().title('Next Token').describe('Token for pagination from previous request'), }), }, output: { schema: z.object({ users: z .array( z.object({ id: z.string().title('ID').describe('User ID'), name: z.string().title('Name').describe('User display name'), email: z.string().title('Email').describe('User email address'), avatarURL: z.string().optional().title('Avatar URL').describe('User avatar URL'), userId: z.string().title('User ID').describe('Your internal user ID'), // Can be empty string if user has no userId isAdmin: z.boolean().title('Is Admin').describe('Whether the user is an admin'), created: z.string().title('Created').describe('User creation timestamp'), }) ) .title('Users') .describe('Array of users'), nextToken: z.string().optional().title('Next Token').describe('Token for next page if more users available'), }), }, }, // Board management actions listBoards: { title: 'List Boards', description: 'List all boards in your Canny workspace', input: { schema: z.object({ limit: z.number().optional().title('Limit').describe('Number of boards to fetch'), nextToken: z.number().optional().title('Next Token').describe('Token for pagination - skip this many boards'), }), }, output: { schema: z.object({ boards: z .array( z.object({ id: z.string().title('ID').describe('Board ID'), name: z.string().title('Name').describe('Board name'), postCount: z.number().title('Post Count').describe('Number of posts in the board'), url: z.string().title('URL').describe('Board URL'), created: z.string().title('Created').describe('Board creation timestamp'), }) ) .title('Boards') .describe('Array of boards'), nextToken: z.number().optional().title('Next Token').describe('Token for next page if more boards available'), }), }, }, }, attributes: { category: 'Project Management', guideSlug: 'canny', repo: 'botpress', }, })