@slates-integrations/zoho 0.2.0-rc.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,109 @@
1
+ import { SlateTool } from 'slates';
2
+ import { z } from 'zod';
3
+ import { spec } from '../spec';
4
+ import { ZohoCrmClient } from '../lib/client';
5
+ import type { Datacenter } from '../lib/urls';
6
+ import { zohoServiceError } from '../lib/errors';
7
+
8
+ export let crmSearchRecords = SlateTool.create(spec, {
9
+ name: 'CRM Search Records',
10
+ key: 'crm_search_records',
11
+ description: `Search for records in a Zoho CRM module using criteria, email, phone, or keyword. Also supports executing COQL (CRM Object Query Language) queries for more complex data retrieval similar to SQL SELECT statements.`,
12
+ instructions: [
13
+ 'For basic search, provide one of: criteria, email, phone, or word.',
14
+ 'Criteria format: `((field_api_name:operator:value))` — operators include equals, not_equal, starts_with, contains, greater_than, less_than, between, in.',
15
+ 'Multiple criteria can be combined with `and`/`or`: `((Last_Name:equals:Smith)and(Company:equals:Acme))`.',
16
+ "For COQL queries, provide a coqlQuery string like `select Last_Name, Email from Leads where Company = 'Acme' limit 10`.",
17
+ 'Search results are indexed asynchronously — newly created records may not appear immediately. Use COQL for real-time results.'
18
+ ],
19
+ constraints: [
20
+ 'Search returns a maximum of 2000 records.',
21
+ 'Only one search parameter (criteria, email, phone, word) is used at a time, with priority: criteria > email > phone > word.'
22
+ ],
23
+ tags: {
24
+ readOnly: true
25
+ }
26
+ })
27
+ .input(
28
+ z.object({
29
+ module: z
30
+ .string()
31
+ .optional()
32
+ .describe('CRM module API name (required for search, not needed for COQL)'),
33
+ criteria: z
34
+ .string()
35
+ .optional()
36
+ .describe('Search criteria expression, e.g., ((Last_Name:equals:Smith))'),
37
+ email: z.string().optional().describe('Search by email address across all email fields'),
38
+ phone: z.string().optional().describe('Search by phone number across all phone fields'),
39
+ word: z.string().optional().describe('Keyword search across all text fields'),
40
+ coqlQuery: z
41
+ .string()
42
+ .optional()
43
+ .describe(
44
+ 'COQL query string (e.g., "select Last_Name, Email from Leads where Company = \'Acme\'")'
45
+ ),
46
+ fields: z.string().optional().describe('Comma-separated field API names to return'),
47
+ converted: z
48
+ .enum(['true', 'false', 'both'])
49
+ .optional()
50
+ .describe('Converted-record filter'),
51
+ approved: z.enum(['true', 'false', 'both']).optional().describe('Approval-state filter'),
52
+ userType: z
53
+ .string()
54
+ .optional()
55
+ .describe('Users module type filter, such as "ActiveUsers" or "CurrentUser"'),
56
+ page: z.number().optional().describe('Page number for pagination'),
57
+ perPage: z.number().optional().describe('Records per page (max 200)')
58
+ })
59
+ )
60
+ .output(
61
+ z.object({
62
+ records: z.array(z.record(z.string(), z.any())).describe('Matching records'),
63
+ count: z.number().optional().describe('Number of records returned'),
64
+ moreRecords: z.boolean().optional().describe('Whether more records are available')
65
+ })
66
+ )
67
+ .handleInvocation(async ctx => {
68
+ let dc = (ctx.auth.datacenter || ctx.config.datacenter || 'us') as Datacenter;
69
+ let client = new ZohoCrmClient({ token: ctx.auth.token, datacenter: dc });
70
+
71
+ if (ctx.input.coqlQuery) {
72
+ let result = await client.executeCoql(ctx.input.coqlQuery);
73
+ let records = result?.data || [];
74
+ return {
75
+ output: {
76
+ records,
77
+ count: result?.info?.count ?? records.length,
78
+ moreRecords: result?.info?.more_records ?? false
79
+ },
80
+ message: `COQL query returned **${records.length}** records.`
81
+ };
82
+ }
83
+
84
+ if (!ctx.input.module) throw zohoServiceError('module is required when not using COQL');
85
+
86
+ let result = await client.searchRecords(ctx.input.module, {
87
+ criteria: ctx.input.criteria,
88
+ email: ctx.input.email,
89
+ phone: ctx.input.phone,
90
+ word: ctx.input.word,
91
+ page: ctx.input.page,
92
+ perPage: ctx.input.perPage,
93
+ fields: ctx.input.fields,
94
+ converted: ctx.input.converted,
95
+ approved: ctx.input.approved,
96
+ userType: ctx.input.userType
97
+ });
98
+
99
+ let records = result?.data || [];
100
+ return {
101
+ output: {
102
+ records,
103
+ count: result?.info?.count ?? records.length,
104
+ moreRecords: result?.info?.more_records ?? false
105
+ },
106
+ message: `Search in **${ctx.input.module}** returned **${records.length}** records.`
107
+ };
108
+ })
109
+ .build();
@@ -0,0 +1,146 @@
1
+ import { SlateTool } from 'slates';
2
+ import { z } from 'zod';
3
+ import { spec } from '../spec';
4
+ import { ZohoDeskClient } from '../lib/client';
5
+ import type { Datacenter } from '../lib/urls';
6
+ import { zohoServiceError } from '../lib/errors';
7
+
8
+ export let deskGetTickets = SlateTool.create(spec, {
9
+ name: 'Desk Get Tickets',
10
+ key: 'desk_get_tickets',
11
+ description: `List, search, or retrieve support tickets from Zoho Desk. Supports filtering by department, status, and keyword search. Can also list departments for reference.`,
12
+ instructions: [
13
+ 'The orgId is required for all Zoho Desk operations.',
14
+ 'Provide ticketId to fetch a single ticket, or omit to list/search tickets.',
15
+ 'Use searchQuery for keyword-based search.',
16
+ 'Set includeDepartments to true to also return department list.'
17
+ ],
18
+ tags: {
19
+ readOnly: true
20
+ }
21
+ })
22
+ .input(
23
+ z.object({
24
+ orgId: z
25
+ .string()
26
+ .optional()
27
+ .describe('Zoho Desk organization ID (required unless listOrganizations is true)'),
28
+ listOrganizations: z
29
+ .boolean()
30
+ .optional()
31
+ .describe('If true, list Desk organizations instead of tickets'),
32
+ ticketId: z.string().optional().describe('Specific ticket ID to fetch'),
33
+ searchQuery: z.string().optional().describe('Search keyword to find tickets'),
34
+ departmentId: z.string().optional().describe('Filter by department ID'),
35
+ status: z.string().optional().describe('Filter by ticket status'),
36
+ from: z.number().optional().describe('Starting index for pagination (default 0)'),
37
+ limit: z.number().optional().describe('Number of tickets to return (max 100)'),
38
+ sortBy: z
39
+ .string()
40
+ .optional()
41
+ .describe('Field to sort by (e.g., "modifiedTime", "createdTime")'),
42
+ sortOrder: z.enum(['asc', 'desc']).optional().describe('Sort order'),
43
+ includeDepartments: z
44
+ .boolean()
45
+ .optional()
46
+ .describe('Also return the list of departments')
47
+ })
48
+ )
49
+ .output(
50
+ z.object({
51
+ tickets: z.array(z.record(z.string(), z.any())).describe('Support tickets'),
52
+ organizations: z
53
+ .array(
54
+ z.object({
55
+ organizationId: z.string(),
56
+ name: z.string().optional(),
57
+ isDefault: z.boolean().optional()
58
+ })
59
+ )
60
+ .optional()
61
+ .describe('Desk organizations (if listOrganizations is true)'),
62
+ departments: z
63
+ .array(
64
+ z.object({
65
+ departmentId: z.string(),
66
+ name: z.string()
67
+ })
68
+ )
69
+ .optional()
70
+ .describe('Departments (if includeDepartments is true)')
71
+ })
72
+ )
73
+ .handleInvocation(async ctx => {
74
+ let dc = (ctx.auth.datacenter || ctx.config.datacenter || 'us') as Datacenter;
75
+
76
+ if (ctx.input.listOrganizations) {
77
+ let result = await ZohoDeskClient.listOrganizations(ctx.auth.token, dc);
78
+ let organizationRecords = result?.data || result?.organizations || result || [];
79
+ if (!Array.isArray(organizationRecords)) organizationRecords = [];
80
+ let organizations = organizationRecords.map((org: any) => ({
81
+ organizationId: String(org.id),
82
+ name: org.companyName || org.portalName,
83
+ isDefault: org.isDefault === true || org.isDefault === 'true'
84
+ }));
85
+ return {
86
+ output: { tickets: [], organizations },
87
+ message: `Found **${organizations.length}** Zoho Desk organizations.`
88
+ };
89
+ }
90
+
91
+ if (!ctx.input.orgId) {
92
+ throw zohoServiceError('orgId is required unless listOrganizations is true');
93
+ }
94
+
95
+ let client = new ZohoDeskClient({
96
+ token: ctx.auth.token,
97
+ datacenter: dc,
98
+ orgId: ctx.input.orgId
99
+ });
100
+
101
+ if (ctx.input.ticketId) {
102
+ let ticket = await client.getTicket(ctx.input.ticketId);
103
+ return {
104
+ output: { tickets: [ticket] },
105
+ message: `Fetched ticket **#${ticket?.ticketNumber || ctx.input.ticketId}**.`
106
+ };
107
+ }
108
+
109
+ let tickets: any[];
110
+ if (ctx.input.searchQuery) {
111
+ let result = await client.searchTickets({
112
+ searchStr: ctx.input.searchQuery,
113
+ departmentId: ctx.input.departmentId,
114
+ from: ctx.input.from,
115
+ limit: ctx.input.limit,
116
+ statusType: ctx.input.status
117
+ });
118
+ tickets = result?.data || [];
119
+ } else {
120
+ let result = await client.listTickets({
121
+ departmentId: ctx.input.departmentId,
122
+ status: ctx.input.status,
123
+ from: ctx.input.from,
124
+ limit: ctx.input.limit,
125
+ sortBy: ctx.input.sortBy,
126
+ sortOrder: ctx.input.sortOrder
127
+ });
128
+ tickets = result?.data || result || [];
129
+ if (!Array.isArray(tickets)) tickets = [];
130
+ }
131
+
132
+ let departments: any[] | undefined;
133
+ if (ctx.input.includeDepartments) {
134
+ let deptResult = await client.getDepartments();
135
+ departments = (deptResult?.data || deptResult || []).map((d: any) => ({
136
+ departmentId: String(d.id),
137
+ name: d.name
138
+ }));
139
+ }
140
+
141
+ return {
142
+ output: { tickets, departments },
143
+ message: `Retrieved **${tickets.length}** tickets${departments ? ` and **${departments.length}** departments` : ''}.`
144
+ };
145
+ })
146
+ .build();
@@ -0,0 +1,125 @@
1
+ import { SlateTool } from 'slates';
2
+ import { z } from 'zod';
3
+ import { spec } from '../spec';
4
+ import { ZohoDeskClient } from '../lib/client';
5
+ import type { Datacenter } from '../lib/urls';
6
+ import { zohoServiceError } from '../lib/errors';
7
+
8
+ export let deskManageContact = SlateTool.create(spec, {
9
+ name: 'Desk Manage Contact',
10
+ key: 'desk_manage_contact',
11
+ description:
12
+ 'List, get, create, update, or delete Zoho Desk contacts. Useful for managing requesters before creating support tickets.',
13
+ instructions: [
14
+ 'The orgId parameter is required for all contact operations.',
15
+ 'For create, provide at least lastName or email depending on your Desk portal requirements.',
16
+ 'For update, get, and delete, provide contactId.'
17
+ ],
18
+ tags: {
19
+ destructive: true
20
+ }
21
+ })
22
+ .input(
23
+ z.object({
24
+ orgId: z.string().describe('Zoho Desk organization ID'),
25
+ action: z.enum(['list', 'get', 'create', 'update', 'delete']).describe('Operation'),
26
+ contactId: z.string().optional().describe('Contact ID for get, update, and delete'),
27
+ firstName: z.string().optional().describe('Contact first name'),
28
+ lastName: z.string().optional().describe('Contact last name'),
29
+ email: z.string().optional().describe('Contact email address'),
30
+ phone: z.string().optional().describe('Contact phone number'),
31
+ mobile: z.string().optional().describe('Contact mobile number'),
32
+ accountId: z.string().optional().describe('Associated Desk account ID'),
33
+ description: z.string().optional().describe('Contact description'),
34
+ customFields: z
35
+ .record(z.string(), z.any())
36
+ .optional()
37
+ .describe('Custom field values as key-value pairs'),
38
+ from: z.number().optional().describe('Starting index for list pagination'),
39
+ limit: z.number().optional().describe('Number of contacts to list, max 100'),
40
+ sortBy: z
41
+ .string()
42
+ .optional()
43
+ .describe('Sort field, e.g. "firstName", "lastName", "email", or "modifiedTime"')
44
+ })
45
+ )
46
+ .output(
47
+ z.object({
48
+ contact: z.record(z.string(), z.any()).optional().describe('Contact record'),
49
+ contacts: z.array(z.record(z.string(), z.any())).optional().describe('Contact records'),
50
+ contactId: z.string().optional().describe('Contact ID'),
51
+ deleted: z.boolean().optional()
52
+ })
53
+ )
54
+ .handleInvocation(async ctx => {
55
+ let dc = (ctx.auth.datacenter || ctx.config.datacenter || 'us') as Datacenter;
56
+ let client = new ZohoDeskClient({
57
+ token: ctx.auth.token,
58
+ datacenter: dc,
59
+ orgId: ctx.input.orgId
60
+ });
61
+
62
+ if (ctx.input.action === 'list') {
63
+ let result = await client.listContacts({
64
+ from: ctx.input.from,
65
+ limit: ctx.input.limit,
66
+ sortBy: ctx.input.sortBy
67
+ });
68
+ let contacts = result?.data || result || [];
69
+ return {
70
+ output: { contacts: Array.isArray(contacts) ? contacts : [] },
71
+ message: `Retrieved **${Array.isArray(contacts) ? contacts.length : 0}** Desk contacts.`
72
+ };
73
+ }
74
+
75
+ if (ctx.input.action === 'get') {
76
+ if (!ctx.input.contactId) throw zohoServiceError('contactId is required for get');
77
+ let contact = await client.getContact(ctx.input.contactId);
78
+ return {
79
+ output: { contact, contactId: contact?.id || ctx.input.contactId },
80
+ message: `Fetched Desk contact **${contact?.lastName || ctx.input.contactId}**.`
81
+ };
82
+ }
83
+
84
+ let buildData = () => {
85
+ let data: Record<string, any> = {};
86
+ if (ctx.input.firstName) data.firstName = ctx.input.firstName;
87
+ if (ctx.input.lastName) data.lastName = ctx.input.lastName;
88
+ if (ctx.input.email) data.email = ctx.input.email;
89
+ if (ctx.input.phone) data.phone = ctx.input.phone;
90
+ if (ctx.input.mobile) data.mobile = ctx.input.mobile;
91
+ if (ctx.input.accountId) data.accountId = ctx.input.accountId;
92
+ if (ctx.input.description) data.description = ctx.input.description;
93
+ if (ctx.input.customFields) data.cf = ctx.input.customFields;
94
+ return data;
95
+ };
96
+
97
+ if (ctx.input.action === 'create') {
98
+ let contact = await client.createContact(buildData());
99
+ return {
100
+ output: { contact, contactId: contact?.id },
101
+ message: `Created Desk contact **${contact?.lastName || contact?.email || contact?.id}**.`
102
+ };
103
+ }
104
+
105
+ if (ctx.input.action === 'update') {
106
+ if (!ctx.input.contactId) throw zohoServiceError('contactId is required for update');
107
+ let contact = await client.updateContact(ctx.input.contactId, buildData());
108
+ return {
109
+ output: { contact, contactId: contact?.id || ctx.input.contactId },
110
+ message: `Updated Desk contact **${ctx.input.contactId}**.`
111
+ };
112
+ }
113
+
114
+ if (ctx.input.action === 'delete') {
115
+ if (!ctx.input.contactId) throw zohoServiceError('contactId is required for delete');
116
+ await client.deleteContact(ctx.input.contactId);
117
+ return {
118
+ output: { contactId: ctx.input.contactId, deleted: true },
119
+ message: `Deleted Desk contact **${ctx.input.contactId}**.`
120
+ };
121
+ }
122
+
123
+ throw zohoServiceError('Invalid Desk contact action.');
124
+ })
125
+ .build();
@@ -0,0 +1,126 @@
1
+ import { SlateTool } from 'slates';
2
+ import { z } from 'zod';
3
+ import { spec } from '../spec';
4
+ import { ZohoDeskClient } from '../lib/client';
5
+ import type { Datacenter } from '../lib/urls';
6
+ import { zohoServiceError } from '../lib/errors';
7
+
8
+ export let deskManageTicket = SlateTool.create(spec, {
9
+ name: 'Desk Manage Ticket',
10
+ key: 'desk_manage_ticket',
11
+ description: `Create, update, or delete support tickets in Zoho Desk. Supports setting subject, description, priority, status, assignee, department, and custom fields.`,
12
+ instructions: [
13
+ 'The orgId parameter is required for all Zoho Desk operations.',
14
+ 'For create, provide subject and optionally departmentId, contactId, description, priority, status, etc.',
15
+ 'For update, provide ticketId and the fields to change.',
16
+ 'For delete, provide ticketId.'
17
+ ],
18
+ tags: {
19
+ destructive: true
20
+ }
21
+ })
22
+ .input(
23
+ z.object({
24
+ orgId: z.string().describe('Zoho Desk organization ID'),
25
+ action: z.enum(['create', 'update', 'delete']).describe('Operation to perform'),
26
+ ticketId: z.string().optional().describe('Ticket ID (required for update and delete)'),
27
+ subject: z.string().optional().describe('Ticket subject (required for create)'),
28
+ description: z.string().optional().describe('Ticket description/content'),
29
+ departmentId: z.string().optional().describe('Department ID to assign the ticket to'),
30
+ contactId: z.string().optional().describe('Contact ID associated with the ticket'),
31
+ email: z.string().optional().describe('Email of the requester'),
32
+ priority: z
33
+ .string()
34
+ .optional()
35
+ .describe('Ticket priority (e.g., "High", "Medium", "Low")'),
36
+ status: z
37
+ .string()
38
+ .optional()
39
+ .describe('Ticket status (e.g., "Open", "On Hold", "Escalated", "Closed")'),
40
+ assigneeId: z.string().optional().describe('Agent ID to assign the ticket to'),
41
+ category: z.string().optional().describe('Ticket category'),
42
+ customFields: z
43
+ .record(z.string(), z.any())
44
+ .optional()
45
+ .describe('Custom field values as key-value pairs')
46
+ })
47
+ )
48
+ .output(
49
+ z.object({
50
+ ticketId: z.string().optional(),
51
+ ticketNumber: z.string().optional(),
52
+ subject: z.string().optional(),
53
+ status: z.string().optional(),
54
+ deleted: z.boolean().optional()
55
+ })
56
+ )
57
+ .handleInvocation(async ctx => {
58
+ let dc = (ctx.auth.datacenter || ctx.config.datacenter || 'us') as Datacenter;
59
+ let client = new ZohoDeskClient({
60
+ token: ctx.auth.token,
61
+ datacenter: dc,
62
+ orgId: ctx.input.orgId
63
+ });
64
+
65
+ if (ctx.input.action === 'create') {
66
+ if (!ctx.input.subject) throw zohoServiceError('subject is required for create');
67
+ let data: Record<string, any> = {};
68
+ if (ctx.input.subject) data.subject = ctx.input.subject;
69
+ if (ctx.input.description) data.description = ctx.input.description;
70
+ if (ctx.input.departmentId) data.departmentId = ctx.input.departmentId;
71
+ if (ctx.input.contactId) data.contactId = ctx.input.contactId;
72
+ if (ctx.input.email) data.email = ctx.input.email;
73
+ if (ctx.input.priority) data.priority = ctx.input.priority;
74
+ if (ctx.input.status) data.status = ctx.input.status;
75
+ if (ctx.input.assigneeId) data.assigneeId = ctx.input.assigneeId;
76
+ if (ctx.input.category) data.category = ctx.input.category;
77
+ if (ctx.input.customFields) data.cf = ctx.input.customFields;
78
+
79
+ let result = await client.createTicket(data);
80
+ return {
81
+ output: {
82
+ ticketId: result?.id,
83
+ ticketNumber: result?.ticketNumber,
84
+ subject: result?.subject,
85
+ status: result?.status
86
+ },
87
+ message: `Created ticket **#${result?.ticketNumber}**: "${result?.subject}".`
88
+ };
89
+ }
90
+
91
+ if (ctx.input.action === 'update') {
92
+ if (!ctx.input.ticketId) throw zohoServiceError('ticketId is required for update');
93
+ let data: Record<string, any> = {};
94
+ if (ctx.input.subject) data.subject = ctx.input.subject;
95
+ if (ctx.input.description) data.description = ctx.input.description;
96
+ if (ctx.input.departmentId) data.departmentId = ctx.input.departmentId;
97
+ if (ctx.input.priority) data.priority = ctx.input.priority;
98
+ if (ctx.input.status) data.status = ctx.input.status;
99
+ if (ctx.input.assigneeId) data.assigneeId = ctx.input.assigneeId;
100
+ if (ctx.input.category) data.category = ctx.input.category;
101
+ if (ctx.input.customFields) data.cf = ctx.input.customFields;
102
+
103
+ let result = await client.updateTicket(ctx.input.ticketId, data);
104
+ return {
105
+ output: {
106
+ ticketId: result?.id,
107
+ ticketNumber: result?.ticketNumber,
108
+ subject: result?.subject,
109
+ status: result?.status
110
+ },
111
+ message: `Updated ticket **${ctx.input.ticketId}**.`
112
+ };
113
+ }
114
+
115
+ if (ctx.input.action === 'delete') {
116
+ if (!ctx.input.ticketId) throw zohoServiceError('ticketId is required for delete');
117
+ await client.deleteTicket(ctx.input.ticketId);
118
+ return {
119
+ output: { ticketId: ctx.input.ticketId, deleted: true },
120
+ message: `Deleted ticket **${ctx.input.ticketId}**.`
121
+ };
122
+ }
123
+
124
+ throw zohoServiceError('Invalid Desk ticket action.');
125
+ })
126
+ .build();
@@ -0,0 +1,16 @@
1
+ export { crmGetRecords } from './crm-get-records';
2
+ export { crmManageRecord } from './crm-manage-record';
3
+ export { crmSearchRecords } from './crm-search-records';
4
+ export { crmGetModules } from './crm-get-modules';
5
+ export { crmGetRelatedRecords } from './crm-get-related-records';
6
+ export { deskGetTickets } from './desk-get-tickets';
7
+ export { deskManageTicket } from './desk-manage-ticket';
8
+ export { deskManageContact } from './desk-manage-contact';
9
+ export { booksGetInvoices } from './books-get-invoices';
10
+ export { booksManageInvoice } from './books-manage-invoice';
11
+ export { booksManageContact } from './books-manage-contact';
12
+ export { booksManageExpense } from './books-manage-expense';
13
+ export { peopleManageEmployee } from './people-manage-employee';
14
+ export { projectsGetPortals } from './projects-get-portals';
15
+ export { projectsManageProject } from './projects-manage-project';
16
+ export { projectsManageTask } from './projects-manage-task';
@@ -0,0 +1,163 @@
1
+ import { SlateTool } from 'slates';
2
+ import { z } from 'zod';
3
+ import { spec } from '../spec';
4
+ import { ZohoPeopleClient } from '../lib/client';
5
+ import type { Datacenter } from '../lib/urls';
6
+ import { zohoServiceError } from '../lib/errors';
7
+
8
+ export let peopleManageEmployee = SlateTool.create(spec, {
9
+ name: 'People Manage Employee',
10
+ key: 'people_manage_employee',
11
+ description: `List, search, create, or update employee records in Zoho People. Works with any form in Zoho People (employee, department, etc.). Also supports fetching attendance and leave information.`,
12
+ instructions: [
13
+ 'Use formLinkName "P_Employee" or "employee" for the employee form.',
14
+ 'For search, provide searchColumn and searchValue (e.g., searchColumn="EMPLOYEEMAILALIAS", searchValue="john@example.com").',
15
+ 'For attendance, provide startDate and endDate in "dd-MMM-yyyy" format (e.g., "01-Jan-2024").'
16
+ ],
17
+ tags: {
18
+ destructive: true
19
+ }
20
+ })
21
+ .input(
22
+ z.object({
23
+ action: z
24
+ .enum(['forms', 'list', 'get', 'create', 'update', 'attendance', 'leave_types'])
25
+ .describe('Operation to perform'),
26
+ formLinkName: z
27
+ .string()
28
+ .optional()
29
+ .describe(
30
+ 'Form link name (e.g., "P_Employee", "employee"). Required for list/get/create/update.'
31
+ ),
32
+ recordId: z.string().optional().describe('Record ID (required for get, update)'),
33
+ recordData: z
34
+ .record(z.string(), z.any())
35
+ .optional()
36
+ .describe('Record field data as key-value pairs (for create/update)'),
37
+ searchColumn: z
38
+ .string()
39
+ .optional()
40
+ .describe('Column to search by (e.g., "EMPLOYEEMAILALIAS")'),
41
+ searchValue: z.string().optional().describe('Value to search for'),
42
+ startIndex: z.number().optional().describe('Start index for pagination (default 1)'),
43
+ limit: z.number().optional().describe('Number of records to return (max 200)'),
44
+ startDate: z
45
+ .string()
46
+ .optional()
47
+ .describe('Start date for attendance query (dd-MMM-yyyy)'),
48
+ endDate: z.string().optional().describe('End date for attendance query (dd-MMM-yyyy)'),
49
+ employeeId: z.string().optional().describe('Employee ID for attendance query')
50
+ })
51
+ )
52
+ .output(
53
+ z.object({
54
+ records: z
55
+ .array(z.record(z.string(), z.any()))
56
+ .optional()
57
+ .describe('Employee/form records'),
58
+ forms: z
59
+ .array(z.record(z.string(), z.any()))
60
+ .optional()
61
+ .describe('Available Zoho People forms'),
62
+ record: z
63
+ .record(z.string(), z.any())
64
+ .optional()
65
+ .describe('Single record (for get/create/update)'),
66
+ leaveTypes: z
67
+ .array(z.record(z.string(), z.any()))
68
+ .optional()
69
+ .describe('Leave type details'),
70
+ attendance: z.any().optional().describe('Attendance entries'),
71
+ apiResponse: z.any().optional().describe('Raw API response for complex results')
72
+ })
73
+ )
74
+ .handleInvocation(async ctx => {
75
+ let dc = (ctx.auth.datacenter || ctx.config.datacenter || 'us') as Datacenter;
76
+ let client = new ZohoPeopleClient({ token: ctx.auth.token, datacenter: dc });
77
+
78
+ if (ctx.input.action === 'forms') {
79
+ let result = await client.listForms();
80
+ let forms = result?.response?.result || result?.forms || result || [];
81
+ return {
82
+ output: { forms: Array.isArray(forms) ? forms : [], apiResponse: result },
83
+ message: `Retrieved **${Array.isArray(forms) ? forms.length : 0}** Zoho People forms.`
84
+ };
85
+ }
86
+
87
+ if (ctx.input.action === 'list') {
88
+ if (!ctx.input.formLinkName) throw zohoServiceError('formLinkName is required for list');
89
+ let result = await client.getFormRecords(ctx.input.formLinkName, {
90
+ sIndex: ctx.input.startIndex,
91
+ limit: ctx.input.limit,
92
+ searchColumn: ctx.input.searchColumn,
93
+ searchValue: ctx.input.searchValue
94
+ });
95
+ let records = Array.isArray(result) ? result : result?.data || [];
96
+ return {
97
+ output: { records, apiResponse: result },
98
+ message: `Retrieved **${records.length}** records from **${ctx.input.formLinkName}**.`
99
+ };
100
+ }
101
+
102
+ if (ctx.input.action === 'get') {
103
+ if (!ctx.input.formLinkName) throw zohoServiceError('formLinkName is required for get');
104
+ if (!ctx.input.recordId) throw zohoServiceError('recordId is required for get');
105
+ let result = await client.getFormRecordById(ctx.input.formLinkName, ctx.input.recordId);
106
+ return {
107
+ output: { record: result },
108
+ message: `Fetched record **${ctx.input.recordId}** from **${ctx.input.formLinkName}**.`
109
+ };
110
+ }
111
+
112
+ if (ctx.input.action === 'create') {
113
+ if (!ctx.input.formLinkName)
114
+ throw zohoServiceError('formLinkName is required for create');
115
+ if (!ctx.input.recordData) throw zohoServiceError('recordData is required for create');
116
+ let result = await client.insertFormRecord(ctx.input.formLinkName, ctx.input.recordData);
117
+ return {
118
+ output: { record: result, apiResponse: result },
119
+ message: `Created record in **${ctx.input.formLinkName}**.`
120
+ };
121
+ }
122
+
123
+ if (ctx.input.action === 'update') {
124
+ if (!ctx.input.formLinkName)
125
+ throw zohoServiceError('formLinkName is required for update');
126
+ if (!ctx.input.recordId) throw zohoServiceError('recordId is required for update');
127
+ if (!ctx.input.recordData) throw zohoServiceError('recordData is required for update');
128
+ let result = await client.updateFormRecord(
129
+ ctx.input.formLinkName,
130
+ ctx.input.recordId,
131
+ ctx.input.recordData
132
+ );
133
+ return {
134
+ output: { record: result, apiResponse: result },
135
+ message: `Updated record **${ctx.input.recordId}** in **${ctx.input.formLinkName}**.`
136
+ };
137
+ }
138
+
139
+ if (ctx.input.action === 'leave_types') {
140
+ let result = await client.getLeaveTypes();
141
+ return {
142
+ output: { leaveTypes: result?.response?.result || result },
143
+ message: `Retrieved leave types.`
144
+ };
145
+ }
146
+
147
+ if (ctx.input.action === 'attendance') {
148
+ if (!ctx.input.startDate || !ctx.input.endDate)
149
+ throw zohoServiceError('startDate and endDate are required for attendance');
150
+ let result = await client.getAttendanceEntries({
151
+ sdate: ctx.input.startDate,
152
+ edate: ctx.input.endDate,
153
+ empId: ctx.input.employeeId
154
+ });
155
+ return {
156
+ output: { attendance: result },
157
+ message: `Retrieved attendance entries from **${ctx.input.startDate}** to **${ctx.input.endDate}**.`
158
+ };
159
+ }
160
+
161
+ throw zohoServiceError('Invalid People action.');
162
+ })
163
+ .build();