@symerian/symi 2.1.4 → 2.1.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.
- package/dist/{audio-preflight-5NsCxagO.js → audio-preflight-BZIRAXlv.js} +4 -4
- package/dist/build-info.json +3 -3
- package/dist/bundled/boot-md/handler.js +6 -6
- package/dist/bundled/session-memory/handler.js +6 -6
- package/dist/canvas-host/a2ui/.bundle.hash +1 -1
- package/dist/{chrome-DM65TRMk.js → chrome-791kNCCB.js} +7 -7
- package/dist/{deliver-Cgg1qwKP.js → deliver-CrlXl9KD.js} +1 -1
- package/dist/{image-xvVpGzK5.js → image-ycxQmhff.js} +1 -1
- package/dist/llm-slug-generator.js +6 -6
- package/dist/{pi-embedded-DuWfOlkO.js → pi-embedded-AKhKHxb6.js} +16 -16
- package/dist/{pi-embedded-helpers-Cd124krQ.js → pi-embedded-helpers-DoTustDz.js} +4 -4
- package/dist/{pw-ai-CL_YRdbc.js → pw-ai-DIVguI36.js} +1 -1
- package/dist/{runner-CCo2QuJE.js → runner-Cw5KZrjx.js} +1 -1
- package/dist/{web-BFa88W3e.js → web-Kz-ZuUjq.js} +6 -6
- package/extensions/outlook/index.ts +199 -0
- package/extensions/outlook/package.json +15 -0
- package/extensions/outlook/src/auth.ts +362 -0
- package/extensions/outlook/src/graph-mail.ts +150 -0
- package/extensions/outlook/src/store.ts +72 -0
- package/extensions/outlook/src/tools.ts +256 -0
- package/extensions/outlook/symi.plugin.json +11 -0
- package/package.json +1 -1
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { getAccessToken } from "./store.js";
|
|
2
|
+
|
|
3
|
+
const GRAPH_ROOT = "https://graph.microsoft.com/v1.0";
|
|
4
|
+
|
|
5
|
+
type GraphResponse<T> = { value?: T[]; "@odata.nextLink"?: string };
|
|
6
|
+
|
|
7
|
+
export type MailMessage = {
|
|
8
|
+
id: string;
|
|
9
|
+
subject: string;
|
|
10
|
+
from?: { emailAddress?: { name?: string; address?: string } };
|
|
11
|
+
toRecipients?: Array<{ emailAddress?: { name?: string; address?: string } }>;
|
|
12
|
+
receivedDateTime?: string;
|
|
13
|
+
bodyPreview?: string;
|
|
14
|
+
body?: { contentType?: string; content?: string };
|
|
15
|
+
isRead?: boolean;
|
|
16
|
+
hasAttachments?: boolean;
|
|
17
|
+
importance?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type MailFolder = {
|
|
21
|
+
id: string;
|
|
22
|
+
displayName: string;
|
|
23
|
+
totalItemCount?: number;
|
|
24
|
+
unreadItemCount?: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
async function graphFetch<T>(path: string, init?: RequestInit): Promise<T> {
|
|
28
|
+
const token = await getAccessToken();
|
|
29
|
+
const response = await fetch(`${GRAPH_ROOT}${path}`, {
|
|
30
|
+
...init,
|
|
31
|
+
headers: {
|
|
32
|
+
Authorization: `Bearer ${token}`,
|
|
33
|
+
"Content-Type": "application/json",
|
|
34
|
+
...init?.headers,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
if (!response.ok) {
|
|
38
|
+
const text = await response.text().catch(() => "");
|
|
39
|
+
throw new Error(`Graph API ${path} failed (${response.status}): ${text || "unknown error"}`);
|
|
40
|
+
}
|
|
41
|
+
if (response.status === 204) {
|
|
42
|
+
return {} as T;
|
|
43
|
+
}
|
|
44
|
+
return (await response.json()) as T;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function listMessages(params?: {
|
|
48
|
+
folder?: string;
|
|
49
|
+
top?: number;
|
|
50
|
+
skip?: number;
|
|
51
|
+
filter?: string;
|
|
52
|
+
search?: string;
|
|
53
|
+
}): Promise<MailMessage[]> {
|
|
54
|
+
const folder = params?.folder ?? "inbox";
|
|
55
|
+
const top = params?.top ?? 10;
|
|
56
|
+
const select =
|
|
57
|
+
"id,subject,from,toRecipients,receivedDateTime,bodyPreview,isRead,hasAttachments,importance";
|
|
58
|
+
|
|
59
|
+
const queryParts = [`$top=${top}`, `$select=${select}`, "$orderby=receivedDateTime desc"];
|
|
60
|
+
|
|
61
|
+
if (params?.skip) {
|
|
62
|
+
queryParts.push(`$skip=${params.skip}`);
|
|
63
|
+
}
|
|
64
|
+
if (params?.filter) {
|
|
65
|
+
queryParts.push(`$filter=${encodeURIComponent(params.filter)}`);
|
|
66
|
+
}
|
|
67
|
+
if (params?.search) {
|
|
68
|
+
queryParts.push(`$search="${encodeURIComponent(params.search)}"`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const query = queryParts.join("&");
|
|
72
|
+
const result = await graphFetch<GraphResponse<MailMessage>>(
|
|
73
|
+
`/me/mailFolders/${encodeURIComponent(folder)}/messages?${query}`,
|
|
74
|
+
);
|
|
75
|
+
return result.value ?? [];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function readMessage(messageId: string): Promise<MailMessage> {
|
|
79
|
+
return await graphFetch<MailMessage>(
|
|
80
|
+
`/me/messages/${encodeURIComponent(messageId)}?$select=id,subject,from,toRecipients,receivedDateTime,body,isRead,hasAttachments,importance`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function sendMessage(params: {
|
|
85
|
+
to: string[];
|
|
86
|
+
cc?: string[];
|
|
87
|
+
subject: string;
|
|
88
|
+
body: string;
|
|
89
|
+
contentType?: "Text" | "HTML";
|
|
90
|
+
}): Promise<void> {
|
|
91
|
+
const message = {
|
|
92
|
+
subject: params.subject,
|
|
93
|
+
body: {
|
|
94
|
+
contentType: params.contentType ?? "Text",
|
|
95
|
+
content: params.body,
|
|
96
|
+
},
|
|
97
|
+
toRecipients: params.to.map((addr) => ({
|
|
98
|
+
emailAddress: { address: addr },
|
|
99
|
+
})),
|
|
100
|
+
...(params.cc?.length
|
|
101
|
+
? {
|
|
102
|
+
ccRecipients: params.cc.map((addr) => ({
|
|
103
|
+
emailAddress: { address: addr },
|
|
104
|
+
})),
|
|
105
|
+
}
|
|
106
|
+
: {}),
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
await graphFetch<void>("/me/sendMail", {
|
|
110
|
+
method: "POST",
|
|
111
|
+
body: JSON.stringify({ message, saveToSentItems: true }),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export async function replyToMessage(params: {
|
|
116
|
+
messageId: string;
|
|
117
|
+
body: string;
|
|
118
|
+
replyAll?: boolean;
|
|
119
|
+
}): Promise<void> {
|
|
120
|
+
const action = params.replyAll ? "replyAll" : "reply";
|
|
121
|
+
await graphFetch<void>(`/me/messages/${encodeURIComponent(params.messageId)}/${action}`, {
|
|
122
|
+
method: "POST",
|
|
123
|
+
body: JSON.stringify({ comment: params.body }),
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function searchMessages(query: string, top?: number): Promise<MailMessage[]> {
|
|
128
|
+
return listMessages({ search: query, top: top ?? 10 });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function listFolders(): Promise<MailFolder[]> {
|
|
132
|
+
const result = await graphFetch<GraphResponse<MailFolder>>(
|
|
133
|
+
"/me/mailFolders?$select=id,displayName,totalItemCount,unreadItemCount&$top=50",
|
|
134
|
+
);
|
|
135
|
+
return result.value ?? [];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export async function markAsRead(messageId: string): Promise<void> {
|
|
139
|
+
await graphFetch<void>(`/me/messages/${encodeURIComponent(messageId)}`, {
|
|
140
|
+
method: "PATCH",
|
|
141
|
+
body: JSON.stringify({ isRead: true }),
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function moveMessage(messageId: string, destinationFolder: string): Promise<void> {
|
|
146
|
+
await graphFetch<void>(`/me/messages/${encodeURIComponent(messageId)}/move`, {
|
|
147
|
+
method: "POST",
|
|
148
|
+
body: JSON.stringify({ destinationId: destinationFolder }),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { refreshAccessToken } from "./auth.js";
|
|
4
|
+
|
|
5
|
+
export type OutlookCredentials = {
|
|
6
|
+
access: string;
|
|
7
|
+
refresh: string;
|
|
8
|
+
expires: number;
|
|
9
|
+
email?: string;
|
|
10
|
+
displayName?: string;
|
|
11
|
+
updatedAt?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const STORE_DIR = path.join(
|
|
15
|
+
process.env.HOME || process.env.USERPROFILE || "/tmp",
|
|
16
|
+
".symi",
|
|
17
|
+
"credentials",
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const STORE_PATH = path.join(STORE_DIR, "outlook.json");
|
|
21
|
+
|
|
22
|
+
export function loadCredentials(): OutlookCredentials | null {
|
|
23
|
+
try {
|
|
24
|
+
const data = fs.readFileSync(STORE_PATH, "utf-8");
|
|
25
|
+
return JSON.parse(data) as OutlookCredentials;
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function saveCredentials(creds: OutlookCredentials): void {
|
|
32
|
+
fs.mkdirSync(STORE_DIR, { recursive: true });
|
|
33
|
+
creds.updatedAt = new Date().toISOString();
|
|
34
|
+
fs.writeFileSync(STORE_PATH, JSON.stringify(creds, null, 2), { mode: 0o600 });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function deleteCredentials(): boolean {
|
|
38
|
+
try {
|
|
39
|
+
fs.unlinkSync(STORE_PATH);
|
|
40
|
+
return true;
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Returns a valid access token, refreshing if expired.
|
|
48
|
+
* Throws if no credentials stored or refresh fails.
|
|
49
|
+
*/
|
|
50
|
+
export async function getAccessToken(): Promise<string> {
|
|
51
|
+
const creds = loadCredentials();
|
|
52
|
+
if (!creds) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
"Outlook not connected. Run `symi outlook login` to sign in with your Microsoft account.",
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Token still valid
|
|
59
|
+
if (Date.now() < creds.expires) {
|
|
60
|
+
return creds.access;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Refresh the token
|
|
64
|
+
const refreshed = await refreshAccessToken(creds.refresh);
|
|
65
|
+
saveCredentials({
|
|
66
|
+
...creds,
|
|
67
|
+
access: refreshed.access,
|
|
68
|
+
refresh: refreshed.refresh,
|
|
69
|
+
expires: refreshed.expires,
|
|
70
|
+
});
|
|
71
|
+
return refreshed.access;
|
|
72
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import {
|
|
3
|
+
listMessages,
|
|
4
|
+
readMessage,
|
|
5
|
+
sendMessage,
|
|
6
|
+
replyToMessage,
|
|
7
|
+
searchMessages,
|
|
8
|
+
listFolders,
|
|
9
|
+
markAsRead,
|
|
10
|
+
moveMessage,
|
|
11
|
+
type MailMessage,
|
|
12
|
+
} from "./graph-mail.js";
|
|
13
|
+
|
|
14
|
+
function formatMessage(msg: MailMessage, includeBody = false): string {
|
|
15
|
+
const from = msg.from?.emailAddress;
|
|
16
|
+
const to = msg.toRecipients?.map((r) => r.emailAddress?.address).join(", ") ?? "";
|
|
17
|
+
const lines = [
|
|
18
|
+
`ID: ${msg.id}`,
|
|
19
|
+
`From: ${from?.name ?? ""} <${from?.address ?? ""}>`,
|
|
20
|
+
`To: ${to}`,
|
|
21
|
+
`Subject: ${msg.subject ?? "(no subject)"}`,
|
|
22
|
+
`Date: ${msg.receivedDateTime ?? ""}`,
|
|
23
|
+
`Read: ${msg.isRead ? "yes" : "no"}`,
|
|
24
|
+
msg.hasAttachments ? "Attachments: yes" : "",
|
|
25
|
+
msg.importance && msg.importance !== "normal" ? `Importance: ${msg.importance}` : "",
|
|
26
|
+
].filter(Boolean);
|
|
27
|
+
|
|
28
|
+
if (includeBody && msg.body?.content) {
|
|
29
|
+
const body =
|
|
30
|
+
msg.body.contentType === "html"
|
|
31
|
+
? msg.body.content
|
|
32
|
+
.replace(/<[^>]+>/g, " ")
|
|
33
|
+
.replace(/\s+/g, " ")
|
|
34
|
+
.trim()
|
|
35
|
+
: msg.body.content;
|
|
36
|
+
lines.push("", "--- Body ---", body.slice(0, 8000));
|
|
37
|
+
} else if (msg.bodyPreview) {
|
|
38
|
+
lines.push("", `Preview: ${msg.bodyPreview}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return lines.join("\n");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function createOutlookListTool() {
|
|
45
|
+
return {
|
|
46
|
+
name: "outlook_list",
|
|
47
|
+
label: "Outlook: List Emails",
|
|
48
|
+
description:
|
|
49
|
+
"List recent emails from an Outlook 365 mailbox folder. Returns subject, sender, date, and preview for each message.",
|
|
50
|
+
parameters: Type.Object({
|
|
51
|
+
folder: Type.Optional(
|
|
52
|
+
Type.String({
|
|
53
|
+
description: 'Mail folder (default: "inbox"). Use "sentitems", "drafts", "archive", etc.',
|
|
54
|
+
}),
|
|
55
|
+
),
|
|
56
|
+
count: Type.Optional(
|
|
57
|
+
Type.Number({ description: "Number of emails to return (default: 10, max: 25)" }),
|
|
58
|
+
),
|
|
59
|
+
unread_only: Type.Optional(
|
|
60
|
+
Type.Boolean({ description: "Only show unread emails (default: false)" }),
|
|
61
|
+
),
|
|
62
|
+
}),
|
|
63
|
+
async execute(_id: string, params: Record<string, unknown>) {
|
|
64
|
+
const folder = (params.folder as string) ?? "inbox";
|
|
65
|
+
const count = Math.min(Number(params.count) || 10, 25);
|
|
66
|
+
const filter = params.unread_only ? "isRead eq false" : undefined;
|
|
67
|
+
|
|
68
|
+
const messages = await listMessages({ folder, top: count, filter });
|
|
69
|
+
|
|
70
|
+
if (messages.length === 0) {
|
|
71
|
+
return { content: [{ type: "text", text: `No emails found in ${folder}.` }] };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const text = messages.map((m) => formatMessage(m)).join("\n\n---\n\n");
|
|
75
|
+
return {
|
|
76
|
+
content: [{ type: "text", text: `${messages.length} email(s) from ${folder}:\n\n${text}` }],
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function createOutlookReadTool() {
|
|
83
|
+
return {
|
|
84
|
+
name: "outlook_read",
|
|
85
|
+
label: "Outlook: Read Email",
|
|
86
|
+
description:
|
|
87
|
+
"Read the full content of a specific email by its ID. Returns complete body, headers, and metadata.",
|
|
88
|
+
parameters: Type.Object({
|
|
89
|
+
message_id: Type.String({ description: "The email message ID to read" }),
|
|
90
|
+
mark_read: Type.Optional(
|
|
91
|
+
Type.Boolean({ description: "Mark the email as read after opening (default: true)" }),
|
|
92
|
+
),
|
|
93
|
+
}),
|
|
94
|
+
async execute(_id: string, params: Record<string, unknown>) {
|
|
95
|
+
const messageId = params.message_id as string;
|
|
96
|
+
if (!messageId) {
|
|
97
|
+
throw new Error("message_id is required");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const msg = await readMessage(messageId);
|
|
101
|
+
const shouldMark = params.mark_read !== false;
|
|
102
|
+
if (shouldMark && !msg.isRead) {
|
|
103
|
+
await markAsRead(messageId).catch(() => {});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { content: [{ type: "text", text: formatMessage(msg, true) }] };
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function createOutlookSendTool() {
|
|
112
|
+
return {
|
|
113
|
+
name: "outlook_send",
|
|
114
|
+
label: "Outlook: Send Email",
|
|
115
|
+
description:
|
|
116
|
+
"Send an email from the connected Outlook 365 account. Supports To, CC, subject, and text body.",
|
|
117
|
+
parameters: Type.Object({
|
|
118
|
+
to: Type.Array(Type.String(), {
|
|
119
|
+
description: "Recipient email addresses",
|
|
120
|
+
minItems: 1,
|
|
121
|
+
}),
|
|
122
|
+
subject: Type.String({ description: "Email subject line" }),
|
|
123
|
+
body: Type.String({ description: "Email body (plain text)" }),
|
|
124
|
+
cc: Type.Optional(Type.Array(Type.String(), { description: "CC recipient email addresses" })),
|
|
125
|
+
}),
|
|
126
|
+
async execute(_id: string, params: Record<string, unknown>) {
|
|
127
|
+
const to = params.to as string[];
|
|
128
|
+
const subject = params.subject as string;
|
|
129
|
+
const body = params.body as string;
|
|
130
|
+
const cc = params.cc as string[] | undefined;
|
|
131
|
+
|
|
132
|
+
if (!to?.length) throw new Error("At least one recipient is required");
|
|
133
|
+
if (!subject?.trim()) throw new Error("Subject is required");
|
|
134
|
+
if (!body?.trim()) throw new Error("Body is required");
|
|
135
|
+
|
|
136
|
+
await sendMessage({ to, cc, subject, body });
|
|
137
|
+
return {
|
|
138
|
+
content: [{ type: "text", text: `Email sent to ${to.join(", ")}.\nSubject: ${subject}` }],
|
|
139
|
+
};
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function createOutlookReplyTool() {
|
|
145
|
+
return {
|
|
146
|
+
name: "outlook_reply",
|
|
147
|
+
label: "Outlook: Reply to Email",
|
|
148
|
+
description: "Reply to an existing email by its ID. Supports reply and reply-all.",
|
|
149
|
+
parameters: Type.Object({
|
|
150
|
+
message_id: Type.String({ description: "The email message ID to reply to" }),
|
|
151
|
+
body: Type.String({ description: "Reply body text" }),
|
|
152
|
+
reply_all: Type.Optional(
|
|
153
|
+
Type.Boolean({ description: "Reply to all recipients (default: false)" }),
|
|
154
|
+
),
|
|
155
|
+
}),
|
|
156
|
+
async execute(_id: string, params: Record<string, unknown>) {
|
|
157
|
+
const messageId = params.message_id as string;
|
|
158
|
+
const body = params.body as string;
|
|
159
|
+
const replyAll = Boolean(params.reply_all);
|
|
160
|
+
|
|
161
|
+
if (!messageId) throw new Error("message_id is required");
|
|
162
|
+
if (!body?.trim()) throw new Error("Reply body is required");
|
|
163
|
+
|
|
164
|
+
await replyToMessage({ messageId, body, replyAll });
|
|
165
|
+
return {
|
|
166
|
+
content: [
|
|
167
|
+
{
|
|
168
|
+
type: "text",
|
|
169
|
+
text: `Reply ${replyAll ? "(all) " : ""}sent to message ${messageId}.`,
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
};
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function createOutlookSearchTool() {
|
|
178
|
+
return {
|
|
179
|
+
name: "outlook_search",
|
|
180
|
+
label: "Outlook: Search Emails",
|
|
181
|
+
description:
|
|
182
|
+
"Search emails in the Outlook 365 mailbox using keywords. Searches across subject, body, and sender.",
|
|
183
|
+
parameters: Type.Object({
|
|
184
|
+
query: Type.String({ description: "Search query (keywords, sender name, subject text)" }),
|
|
185
|
+
count: Type.Optional(
|
|
186
|
+
Type.Number({ description: "Max results to return (default: 10, max: 25)" }),
|
|
187
|
+
),
|
|
188
|
+
}),
|
|
189
|
+
async execute(_id: string, params: Record<string, unknown>) {
|
|
190
|
+
const query = params.query as string;
|
|
191
|
+
const count = Math.min(Number(params.count) || 10, 25);
|
|
192
|
+
|
|
193
|
+
if (!query?.trim()) throw new Error("Search query is required");
|
|
194
|
+
|
|
195
|
+
const messages = await searchMessages(query, count);
|
|
196
|
+
|
|
197
|
+
if (messages.length === 0) {
|
|
198
|
+
return { content: [{ type: "text", text: `No emails found for "${query}".` }] };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const text = messages.map((m) => formatMessage(m)).join("\n\n---\n\n");
|
|
202
|
+
return {
|
|
203
|
+
content: [
|
|
204
|
+
{ type: "text", text: `${messages.length} result(s) for "${query}":\n\n${text}` },
|
|
205
|
+
],
|
|
206
|
+
};
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function createOutlookFoldersTool() {
|
|
212
|
+
return {
|
|
213
|
+
name: "outlook_folders",
|
|
214
|
+
label: "Outlook: List Folders",
|
|
215
|
+
description:
|
|
216
|
+
"List all mail folders in the Outlook 365 mailbox with item counts and unread counts.",
|
|
217
|
+
parameters: Type.Object({}),
|
|
218
|
+
async execute() {
|
|
219
|
+
const folders = await listFolders();
|
|
220
|
+
const text = folders
|
|
221
|
+
.map(
|
|
222
|
+
(f) =>
|
|
223
|
+
`${f.displayName} (${f.unreadItemCount ?? 0} unread / ${f.totalItemCount ?? 0} total) [id: ${f.id}]`,
|
|
224
|
+
)
|
|
225
|
+
.join("\n");
|
|
226
|
+
return { content: [{ type: "text", text: text || "No folders found." }] };
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function createOutlookMoveTool() {
|
|
232
|
+
return {
|
|
233
|
+
name: "outlook_move",
|
|
234
|
+
label: "Outlook: Move Email",
|
|
235
|
+
description: "Move an email to a different folder by message ID and destination folder ID.",
|
|
236
|
+
parameters: Type.Object({
|
|
237
|
+
message_id: Type.String({ description: "The email message ID to move" }),
|
|
238
|
+
destination: Type.String({
|
|
239
|
+
description:
|
|
240
|
+
'Destination folder ID or well-known name ("archive", "deleteditems", "junkemail")',
|
|
241
|
+
}),
|
|
242
|
+
}),
|
|
243
|
+
async execute(_id: string, params: Record<string, unknown>) {
|
|
244
|
+
const messageId = params.message_id as string;
|
|
245
|
+
const destination = params.destination as string;
|
|
246
|
+
|
|
247
|
+
if (!messageId) throw new Error("message_id is required");
|
|
248
|
+
if (!destination) throw new Error("destination folder is required");
|
|
249
|
+
|
|
250
|
+
await moveMessage(messageId, destination);
|
|
251
|
+
return {
|
|
252
|
+
content: [{ type: "text", text: `Email ${messageId} moved to ${destination}.` }],
|
|
253
|
+
};
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "outlook",
|
|
3
|
+
"name": "Outlook 365",
|
|
4
|
+
"description": "Read, send, search, and manage Outlook 365 email via Microsoft Graph API. Sign in with your normal Microsoft account.",
|
|
5
|
+
"providers": ["outlook-mail"],
|
|
6
|
+
"configSchema": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {}
|
|
10
|
+
}
|
|
11
|
+
}
|