@slates-integrations/sharepoint 0.2.0-rc.6

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,262 @@
1
+ import { SlateTool } from 'slates';
2
+ import { SharePointClient } from '../lib/client';
3
+ import { spec } from '../spec';
4
+ import { oneOfRequiredError } from './errors';
5
+ import { z } from 'zod';
6
+
7
+ let fileOutputSchema = z.object({
8
+ itemId: z.string().describe('Drive item ID'),
9
+ fileName: z.string().describe('Name of the file or folder'),
10
+ webUrl: z.string().optional().describe('URL to access the item'),
11
+ downloadUrl: z.string().optional().describe('Direct download URL (for files)'),
12
+ size: z.number().optional().describe('Size in bytes'),
13
+ mimeType: z.string().optional().describe('MIME type of the file'),
14
+ isFolder: z.boolean().describe('Whether this is a folder'),
15
+ createdDateTime: z.string().optional().describe('When the item was created'),
16
+ lastModifiedDateTime: z.string().optional().describe('When the item was last modified'),
17
+ createdBy: z.string().optional().describe('User who created the item'),
18
+ lastModifiedBy: z.string().optional().describe('User who last modified the item'),
19
+ parentPath: z.string().optional().describe('Path of the parent folder')
20
+ });
21
+
22
+ export let manageFile = SlateTool.create(spec, {
23
+ name: 'Manage File',
24
+ key: 'manage_file',
25
+ description: `Upload, download, move, copy, rename, or delete files and folders in a SharePoint document library. Also supports creating folders and listing folder contents. Use this for all file and folder operations within document libraries.`,
26
+ instructions: [
27
+ 'Set **action** to "upload", "download", "get", "list", "createFolder", "move", "copy", "rename", or "delete".',
28
+ 'For **upload**, provide **fileContent** (text/base64), **fileName**, and either **parentPath** or **parentFolderId**.',
29
+ 'For **download**, returns the download URL of the file.',
30
+ "For **list**, provide **driveId** and optionally **folderId** to list a specific folder's contents.",
31
+ 'For **move**, provide **destinationFolderId** in the same drive.',
32
+ 'For **copy**, provide **destinationDriveId** and **destinationFolderId**.'
33
+ ],
34
+ tags: {
35
+ destructive: false,
36
+ readOnly: false
37
+ }
38
+ })
39
+ .input(
40
+ z.object({
41
+ action: z
42
+ .enum([
43
+ 'upload',
44
+ 'download',
45
+ 'get',
46
+ 'list',
47
+ 'createFolder',
48
+ 'move',
49
+ 'copy',
50
+ 'rename',
51
+ 'delete'
52
+ ])
53
+ .describe('File action to perform'),
54
+ driveId: z.string().describe('Drive (document library) ID'),
55
+ itemId: z
56
+ .string()
57
+ .optional()
58
+ .describe('Drive item ID (required for get, download, move, copy, rename, delete)'),
59
+ itemPath: z
60
+ .string()
61
+ .optional()
62
+ .describe(
63
+ 'Path to the item relative to the drive root (alternative to itemId for get)'
64
+ ),
65
+ fileName: z
66
+ .string()
67
+ .optional()
68
+ .describe('File or folder name (for upload, createFolder, rename, copy)'),
69
+ fileContent: z
70
+ .string()
71
+ .optional()
72
+ .describe('File content as text (for upload, max ~4MB)'),
73
+ parentPath: z
74
+ .string()
75
+ .optional()
76
+ .describe('Parent folder path relative to drive root (for upload)'),
77
+ parentFolderId: z
78
+ .string()
79
+ .optional()
80
+ .describe('Parent folder ID (for upload, createFolder)'),
81
+ folderId: z
82
+ .string()
83
+ .optional()
84
+ .describe('Folder ID to list contents of (for list action)'),
85
+ destinationFolderId: z
86
+ .string()
87
+ .optional()
88
+ .describe('Destination folder ID (for move, copy)'),
89
+ destinationDriveId: z
90
+ .string()
91
+ .optional()
92
+ .describe('Destination drive ID (for copy across drives)'),
93
+ newName: z.string().optional().describe('New name (for rename)')
94
+ })
95
+ )
96
+ .output(
97
+ z.object({
98
+ file: fileOutputSchema.optional().describe('File/folder details'),
99
+ files: z
100
+ .array(fileOutputSchema)
101
+ .optional()
102
+ .describe('List of files and folders (for list action)'),
103
+ downloadUrl: z.string().optional().describe('Direct download URL (for download action)'),
104
+ deleted: z.boolean().optional().describe('Whether the item was deleted'),
105
+ copied: z.boolean().optional().describe('Whether the copy was initiated'),
106
+ copyMonitorUrl: z
107
+ .string()
108
+ .optional()
109
+ .describe('Monitor URL returned by Microsoft Graph for async copy operations')
110
+ })
111
+ )
112
+ .handleInvocation(async ctx => {
113
+ let client = new SharePointClient(ctx.auth.token);
114
+ let {
115
+ action,
116
+ driveId,
117
+ itemId,
118
+ itemPath,
119
+ fileName,
120
+ fileContent,
121
+ parentPath,
122
+ parentFolderId,
123
+ folderId,
124
+ destinationFolderId,
125
+ destinationDriveId,
126
+ newName
127
+ } = ctx.input;
128
+
129
+ let mapItem = (item: any) => ({
130
+ itemId: item.id,
131
+ fileName: item.name,
132
+ webUrl: item.webUrl,
133
+ downloadUrl: item['@microsoft.graph.downloadUrl'],
134
+ size: item.size,
135
+ mimeType: item.file?.mimeType,
136
+ isFolder: !!item.folder,
137
+ createdDateTime: item.createdDateTime,
138
+ lastModifiedDateTime: item.lastModifiedDateTime,
139
+ createdBy: item.createdBy?.user?.displayName,
140
+ lastModifiedBy: item.lastModifiedBy?.user?.displayName,
141
+ parentPath: item.parentReference?.path
142
+ });
143
+
144
+ switch (action) {
145
+ case 'get': {
146
+ let item: any;
147
+ if (itemPath) {
148
+ item = await client.getDriveItemByPath(driveId, itemPath);
149
+ } else if (itemId) {
150
+ item = await client.getDriveItem(driveId, itemId);
151
+ } else {
152
+ throw oneOfRequiredError(
153
+ 'For get action, one of itemId or itemPath must be provided.',
154
+ ['itemId', 'itemPath']
155
+ );
156
+ }
157
+ return {
158
+ output: { file: mapItem(item) },
159
+ message: `Retrieved **${item.name}** (${item.folder ? 'folder' : `${item.size} bytes`}).`
160
+ };
161
+ }
162
+
163
+ case 'list': {
164
+ let data = await client.listDriveItems(driveId, folderId);
165
+ let files = (data.value || []).map(mapItem);
166
+ return {
167
+ output: { files },
168
+ message: `Found **${files.length}** item(s) in the folder.`
169
+ };
170
+ }
171
+
172
+ case 'upload': {
173
+ if (!fileName) throw new Error('fileName is required for upload.');
174
+ if (fileContent === undefined) throw new Error('fileContent is required for upload.');
175
+ let item: any;
176
+ if (parentFolderId) {
177
+ item = await client.uploadSmallFileToFolder(
178
+ driveId,
179
+ parentFolderId,
180
+ fileName,
181
+ fileContent
182
+ );
183
+ } else {
184
+ let path = parentPath || '';
185
+ item = await client.uploadSmallFile(driveId, path, fileName, fileContent);
186
+ }
187
+ return {
188
+ output: { file: mapItem(item) },
189
+ message: `Uploaded **${fileName}** (${item.size} bytes).`
190
+ };
191
+ }
192
+
193
+ case 'download': {
194
+ if (!itemId) throw new Error('itemId is required for download.');
195
+ let downloadUrl = await client.getFileDownloadUrl(driveId, itemId);
196
+ return {
197
+ output: { downloadUrl },
198
+ message: `Download URL generated for item \`${itemId}\`.`
199
+ };
200
+ }
201
+
202
+ case 'createFolder': {
203
+ if (!fileName) throw new Error('fileName is required for createFolder.');
204
+ let parent = parentFolderId || 'root';
205
+ let item = await client.createFolder(driveId, parent, fileName);
206
+ return {
207
+ output: { file: mapItem(item) },
208
+ message: `Created folder **${fileName}**.`
209
+ };
210
+ }
211
+
212
+ case 'move': {
213
+ if (!itemId) throw new Error('itemId is required for move.');
214
+ if (!destinationFolderId) throw new Error('destinationFolderId is required for move.');
215
+ let item = await client.moveDriveItem(driveId, itemId, destinationFolderId, newName);
216
+ return {
217
+ output: { file: mapItem(item) },
218
+ message: `Moved item to folder \`${destinationFolderId}\`.`
219
+ };
220
+ }
221
+
222
+ case 'copy': {
223
+ if (!itemId) throw new Error('itemId is required for copy.');
224
+ if (!destinationFolderId) throw new Error('destinationFolderId is required for copy.');
225
+ let targetDrive = destinationDriveId || driveId;
226
+ let copy = await client.copyDriveItem(
227
+ driveId,
228
+ itemId,
229
+ targetDrive,
230
+ destinationFolderId,
231
+ fileName
232
+ );
233
+ return {
234
+ output: {
235
+ copied: true,
236
+ copyMonitorUrl: copy.copyMonitorUrl
237
+ },
238
+ message: `Copy initiated for item \`${itemId}\`.`
239
+ };
240
+ }
241
+
242
+ case 'rename': {
243
+ if (!itemId) throw new Error('itemId is required for rename.');
244
+ if (!newName) throw new Error('newName is required for rename.');
245
+ let item = await client.renameDriveItem(driveId, itemId, newName);
246
+ return {
247
+ output: { file: mapItem(item) },
248
+ message: `Renamed item to **${newName}**.`
249
+ };
250
+ }
251
+
252
+ case 'delete': {
253
+ if (!itemId) throw new Error('itemId is required for delete.');
254
+ await client.deleteDriveItem(driveId, itemId);
255
+ return {
256
+ output: { deleted: true },
257
+ message: `Deleted item \`${itemId}\`.`
258
+ };
259
+ }
260
+ }
261
+ })
262
+ .build();
@@ -0,0 +1,203 @@
1
+ import { SlateTool } from 'slates';
2
+ import { SharePointClient } from '../lib/client';
3
+ import { spec } from '../spec';
4
+ import { z } from 'zod';
5
+
6
+ let listItemOutputSchema = z.object({
7
+ itemId: z.string().describe('List item ID'),
8
+ webUrl: z.string().optional().describe('URL of the list item'),
9
+ createdDateTime: z.string().optional().describe('When the item was created'),
10
+ lastModifiedDateTime: z.string().optional().describe('When the item was last modified'),
11
+ createdBy: z.string().optional().describe('User who created the item'),
12
+ lastModifiedBy: z.string().optional().describe('User who last modified the item'),
13
+ fields: z
14
+ .record(z.string(), z.any())
15
+ .optional()
16
+ .describe('Custom field values of the list item')
17
+ });
18
+
19
+ export let manageListItems = SlateTool.create(spec, {
20
+ name: 'Manage List Items',
21
+ key: 'manage_list_items',
22
+ description: `Full CRUD operations on SharePoint list items. Create, read, update, delete, or list items in a SharePoint list. Supports OData filtering and ordering when listing items. Field values match the list's column schema.`,
23
+ instructions: [
24
+ 'Set **action** to "get", "list", "create", "update", or "delete".',
25
+ 'When creating or updating, pass **fields** as a key-value object matching the list column names.',
26
+ 'When listing, use **filter** for OData filter expressions (e.g. "fields/Status eq \'Active\'") and **orderBy** for sorting.',
27
+ 'For pagination, set **top** to limit page size. If the response includes **nextLink**, pass it as **skipToken** in the next request to get the next page (do not use $skip — SharePoint Lists API does not support it).',
28
+ 'SharePoint requires columns referenced in **filter** or **orderBy** to be indexed. If a query fails with "Field X cannot be referenced in filter or orderby as it is not indexed", either index the column in SharePoint (Site Settings → List settings → Indexed columns) or retry with **allowUnindexedQuery: true** to send the "Prefer: HonorNonIndexedQueriesWarningMayFailRandomly" header (may fail on lists with >5000 items).'
29
+ ],
30
+ constraints: ['Maximum of 5000 items can be returned in a single list request.'],
31
+ tags: {
32
+ destructive: false,
33
+ readOnly: false
34
+ }
35
+ })
36
+ .input(
37
+ z.object({
38
+ action: z
39
+ .enum(['get', 'list', 'create', 'update', 'delete'])
40
+ .describe('Action to perform'),
41
+ siteId: z.string().describe('SharePoint site ID'),
42
+ listId: z.string().describe('SharePoint list ID'),
43
+ itemId: z
44
+ .string()
45
+ .optional()
46
+ .describe('List item ID (required for get, update, delete)'),
47
+ fields: z
48
+ .record(z.string(), z.any())
49
+ .optional()
50
+ .describe('Field values for create or update, keyed by column name'),
51
+ filter: z.string().optional().describe('OData filter expression for list action'),
52
+ orderBy: z.string().optional().describe('OData orderby expression for list action'),
53
+ top: z
54
+ .number()
55
+ .optional()
56
+ .describe('Maximum number of items to return (for list action)'),
57
+ skipToken: z
58
+ .string()
59
+ .optional()
60
+ .describe(
61
+ "Pagination token from a previous response's **nextLink** field; pass this instead of repeating filter/orderBy/top to fetch the next page."
62
+ ),
63
+ allowUnindexedQuery: z
64
+ .boolean()
65
+ .optional()
66
+ .describe(
67
+ 'Set true to send "Prefer: HonorNonIndexedQueriesWarningMayFailRandomly" — lets you filter/orderBy on non-indexed columns. May fail on lists with >5000 items. Prefer indexing the column in SharePoint instead.'
68
+ )
69
+ })
70
+ )
71
+ .output(
72
+ z.object({
73
+ item: listItemOutputSchema
74
+ .optional()
75
+ .describe('Single list item (for get, create, update)'),
76
+ items: z
77
+ .array(listItemOutputSchema)
78
+ .optional()
79
+ .describe('List of items (for list action)'),
80
+ deleted: z
81
+ .boolean()
82
+ .optional()
83
+ .describe('Whether the item was deleted (for delete action)'),
84
+ totalCount: z.number().optional().describe('Number of items returned (for list action)'),
85
+ nextLink: z
86
+ .string()
87
+ .optional()
88
+ .describe('Pagination token — pass as **skipToken** to get the next page')
89
+ })
90
+ )
91
+ .handleInvocation(async ctx => {
92
+ let client = new SharePointClient(ctx.auth.token);
93
+ let {
94
+ action,
95
+ siteId,
96
+ listId,
97
+ itemId,
98
+ fields,
99
+ filter,
100
+ orderBy,
101
+ top,
102
+ skipToken,
103
+ allowUnindexedQuery
104
+ } = ctx.input;
105
+
106
+ let mapItem = (item: any) => ({
107
+ itemId: item.id,
108
+ webUrl: item.webUrl,
109
+ createdDateTime: item.createdDateTime,
110
+ lastModifiedDateTime: item.lastModifiedDateTime,
111
+ createdBy: item.createdBy?.user?.displayName,
112
+ lastModifiedBy: item.lastModifiedBy?.user?.displayName,
113
+ fields: item.fields
114
+ });
115
+
116
+ switch (action) {
117
+ case 'get': {
118
+ if (!itemId) throw new Error('itemId is required for get action.');
119
+ let item = await client.getListItem(siteId, listId, itemId);
120
+ return {
121
+ output: { item: mapItem(item) },
122
+ message: `Retrieved list item \`${itemId}\`.`
123
+ };
124
+ }
125
+
126
+ case 'list': {
127
+ let data: any;
128
+ try {
129
+ data = await client.listListItems(siteId, listId, {
130
+ expand: 'fields',
131
+ filter,
132
+ orderby: orderBy,
133
+ top,
134
+ skipToken,
135
+ allowUnindexedQuery
136
+ });
137
+ } catch (err: any) {
138
+ let apiMsg =
139
+ err?.response?.data?.error?.message ?? err?.response?.data?.message ?? '';
140
+ let combined = `${err?.message || ''} ${apiMsg}`;
141
+ let notIndexed = /is not indexed|HonorNonIndexedQueriesWarningMayFailRandomly/i.test(
142
+ combined
143
+ );
144
+ if (notIndexed && !allowUnindexedQuery) {
145
+ let fieldMatch = combined.match(/Field '([^']+)'/);
146
+ let fieldName = fieldMatch?.[1];
147
+ let status = err?.response?.status;
148
+ let baseMsg = `HTTP ${status ?? 400}: ${apiMsg || err?.message || 'Unindexed column referenced in filter/orderby'}`;
149
+ throw new Error(
150
+ `${baseMsg}\n\nTo resolve, either:\n` +
151
+ ` 1. Index the column${fieldName ? ` '${fieldName}'` : ''} in SharePoint: Site Settings → List settings → Indexed columns → Create a new index. This is the recommended long-term fix.\n` +
152
+ ` 2. Retry this tool with **allowUnindexedQuery: true** to send the "Prefer: HonorNonIndexedQueriesWarningMayFailRandomly" header. Works for lists with <5000 items; may fail on larger lists.`
153
+ );
154
+ }
155
+ throw err;
156
+ }
157
+ let items = (data.value || []).map(mapItem);
158
+ let nextLink: string | undefined = data['@odata.nextLink'];
159
+ return {
160
+ output: { items, totalCount: items.length, nextLink },
161
+ message: `Found **${items.length}** item(s) in the list.${nextLink ? ' More pages available.' : ''}`
162
+ };
163
+ }
164
+
165
+ case 'create': {
166
+ if (!fields || Object.keys(fields).length === 0) {
167
+ throw new Error('fields are required for create action.');
168
+ }
169
+ let item = await client.createListItem(siteId, listId, fields);
170
+ return {
171
+ output: { item: mapItem(item) },
172
+ message: `Created new list item \`${item.id}\`.`
173
+ };
174
+ }
175
+
176
+ case 'update': {
177
+ if (!itemId) throw new Error('itemId is required for update action.');
178
+ if (!fields || Object.keys(fields).length === 0) {
179
+ throw new Error('fields are required for update action.');
180
+ }
181
+ let updatedFields = await client.updateListItem(siteId, listId, itemId, fields);
182
+ return {
183
+ output: {
184
+ item: {
185
+ itemId,
186
+ fields: updatedFields
187
+ }
188
+ },
189
+ message: `Updated list item \`${itemId}\`.`
190
+ };
191
+ }
192
+
193
+ case 'delete': {
194
+ if (!itemId) throw new Error('itemId is required for delete action.');
195
+ await client.deleteListItem(siteId, listId, itemId);
196
+ return {
197
+ output: { deleted: true },
198
+ message: `Deleted list item \`${itemId}\`.`
199
+ };
200
+ }
201
+ }
202
+ })
203
+ .build();
@@ -0,0 +1,133 @@
1
+ import { SlateTool } from 'slates';
2
+ import { SharePointClient } from '../lib/client';
3
+ import { spec } from '../spec';
4
+ import { z } from 'zod';
5
+
6
+ let listOutputSchema = z.object({
7
+ listId: z.string().describe('Unique list ID'),
8
+ listName: z.string().describe('Display name of the list'),
9
+ listDescription: z.string().nullable().describe('List description'),
10
+ webUrl: z.string().optional().describe('URL of the list'),
11
+ createdDateTime: z.string().optional().describe('When the list was created'),
12
+ lastModifiedDateTime: z.string().optional().describe('When the list was last modified'),
13
+ template: z.string().optional().describe('List template type'),
14
+ itemCount: z.number().optional().describe('Number of items in the list')
15
+ });
16
+
17
+ export let manageList = SlateTool.create(spec, {
18
+ name: 'Manage List',
19
+ key: 'manage_list',
20
+ description: `Create, read, update, or delete SharePoint lists within a site. Also supports listing all lists on a site. Lists are the foundation for data storage in SharePoint and can represent custom business data, contact lists, task trackers, and more.`,
21
+ instructions: [
22
+ 'Set **action** to "get" to retrieve a single list, "list" to list all lists on a site, "create" to create a new list, "update" to rename or re-describe a list, or "delete" to remove a list.',
23
+ 'For "create", provide **displayName** and optionally **template** (defaults to "genericList").'
24
+ ],
25
+ tags: {
26
+ destructive: false,
27
+ readOnly: false
28
+ }
29
+ })
30
+ .input(
31
+ z.object({
32
+ action: z
33
+ .enum(['get', 'list', 'create', 'update', 'delete'])
34
+ .describe('Action to perform'),
35
+ siteId: z.string().describe('SharePoint site ID'),
36
+ listId: z.string().optional().describe('List ID (required for get, update, delete)'),
37
+ displayName: z
38
+ .string()
39
+ .optional()
40
+ .describe('Display name for the list (for create/update)'),
41
+ description: z
42
+ .string()
43
+ .optional()
44
+ .describe('Description for the list (for create/update)'),
45
+ template: z
46
+ .string()
47
+ .optional()
48
+ .describe(
49
+ 'List template, e.g. "genericList", "documentLibrary", "events" (for create, defaults to "genericList")'
50
+ )
51
+ })
52
+ )
53
+ .output(
54
+ z.object({
55
+ list: listOutputSchema
56
+ .optional()
57
+ .describe('Single list details (for get, create, update)'),
58
+ lists: z.array(listOutputSchema).optional().describe('All lists (for list action)'),
59
+ deleted: z
60
+ .boolean()
61
+ .optional()
62
+ .describe('Whether the list was deleted (for delete action)')
63
+ })
64
+ )
65
+ .handleInvocation(async ctx => {
66
+ let client = new SharePointClient(ctx.auth.token);
67
+ let { action, siteId, listId, displayName, description, template } = ctx.input;
68
+
69
+ let mapList = (l: any) => ({
70
+ listId: l.id,
71
+ listName: l.displayName,
72
+ listDescription: l.description || null,
73
+ webUrl: l.webUrl,
74
+ createdDateTime: l.createdDateTime,
75
+ lastModifiedDateTime: l.lastModifiedDateTime,
76
+ template: l.list?.template,
77
+ itemCount: l.list?.contentTypesEnabled != null ? undefined : undefined
78
+ });
79
+
80
+ switch (action) {
81
+ case 'get': {
82
+ if (!listId) throw new Error('listId is required for get action.');
83
+ let list = await client.getList(siteId, listId);
84
+ return {
85
+ output: { list: mapList(list) },
86
+ message: `Retrieved list **${list.displayName}** (${list.id}).`
87
+ };
88
+ }
89
+
90
+ case 'list': {
91
+ let data = await client.listLists(siteId);
92
+ let lists = (data.value || []).map(mapList);
93
+ return {
94
+ output: { lists },
95
+ message: `Found **${lists.length}** list(s) on site.`
96
+ };
97
+ }
98
+
99
+ case 'create': {
100
+ if (!displayName) throw new Error('displayName is required for create action.');
101
+ let list = await client.createList(siteId, displayName, template || 'genericList');
102
+ if (description) {
103
+ list = await client.updateList(siteId, list.id, { description });
104
+ }
105
+ return {
106
+ output: { list: mapList(list) },
107
+ message: `Created list **${displayName}**.`
108
+ };
109
+ }
110
+
111
+ case 'update': {
112
+ if (!listId) throw new Error('listId is required for update action.');
113
+ let updates: any = {};
114
+ if (displayName) updates.displayName = displayName;
115
+ if (description !== undefined) updates.description = description;
116
+ let list = await client.updateList(siteId, listId, updates);
117
+ return {
118
+ output: { list: mapList(list) },
119
+ message: `Updated list **${list.displayName}**.`
120
+ };
121
+ }
122
+
123
+ case 'delete': {
124
+ if (!listId) throw new Error('listId is required for delete action.');
125
+ await client.deleteList(siteId, listId);
126
+ return {
127
+ output: { deleted: true },
128
+ message: `Deleted list \`${listId}\`.`
129
+ };
130
+ }
131
+ }
132
+ })
133
+ .build();