@kb-labs/inbox-entry 2.96.0
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/README.md +5 -0
- package/dist/commands/mail-accounts.d.ts +10 -0
- package/dist/commands/mail-accounts.js +104 -0
- package/dist/commands/mail-accounts.js.map +1 -0
- package/dist/commands/mail-delete.d.ts +14 -0
- package/dist/commands/mail-delete.js +103 -0
- package/dist/commands/mail-delete.js.map +1 -0
- package/dist/commands/mail-folders.d.ts +11 -0
- package/dist/commands/mail-folders.js +103 -0
- package/dist/commands/mail-folders.js.map +1 -0
- package/dist/commands/mail-get.d.ts +14 -0
- package/dist/commands/mail-get.js +150 -0
- package/dist/commands/mail-get.js.map +1 -0
- package/dist/commands/mail-list.d.ts +16 -0
- package/dist/commands/mail-list.js +144 -0
- package/dist/commands/mail-list.js.map +1 -0
- package/dist/commands/mail-mark.d.ts +20 -0
- package/dist/commands/mail-mark.js +108 -0
- package/dist/commands/mail-mark.js.map +1 -0
- package/dist/commands/mail-move.d.ts +15 -0
- package/dist/commands/mail-move.js +107 -0
- package/dist/commands/mail-move.js.map +1 -0
- package/dist/commands/mail-reply.d.ts +14 -0
- package/dist/commands/mail-reply.js +114 -0
- package/dist/commands/mail-reply.js.map +1 -0
- package/dist/commands/mail-search.d.ts +19 -0
- package/dist/commands/mail-search.js +146 -0
- package/dist/commands/mail-search.js.map +1 -0
- package/dist/commands/mail-send.d.ts +17 -0
- package/dist/commands/mail-send.js +111 -0
- package/dist/commands/mail-send.js.map +1 -0
- package/dist/commands/mail-thread.d.ts +13 -0
- package/dist/commands/mail-thread.js +137 -0
- package/dist/commands/mail-thread.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +131 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.d.ts +23 -0
- package/dist/manifest.js +132 -0
- package/dist/manifest.js.map +1 -0
- package/dist/rest/handlers/accounts-handler.d.ts +10 -0
- package/dist/rest/handlers/accounts-handler.js +81 -0
- package/dist/rest/handlers/accounts-handler.js.map +1 -0
- package/dist/rest/handlers/delete-handler.d.ts +19 -0
- package/dist/rest/handlers/delete-handler.js +87 -0
- package/dist/rest/handlers/delete-handler.js.map +1 -0
- package/dist/rest/handlers/folders-handler.d.ts +13 -0
- package/dist/rest/handlers/folders-handler.js +82 -0
- package/dist/rest/handlers/folders-handler.js.map +1 -0
- package/dist/rest/handlers/get-handler.d.ts +18 -0
- package/dist/rest/handlers/get-handler.js +90 -0
- package/dist/rest/handlers/get-handler.js.map +1 -0
- package/dist/rest/handlers/list-handler.d.ts +17 -0
- package/dist/rest/handlers/list-handler.js +89 -0
- package/dist/rest/handlers/list-handler.js.map +1 -0
- package/dist/rest/handlers/mark-handler.d.ts +18 -0
- package/dist/rest/handlers/mark-handler.js +87 -0
- package/dist/rest/handlers/mark-handler.js.map +1 -0
- package/dist/rest/handlers/move-handler.d.ts +18 -0
- package/dist/rest/handlers/move-handler.js +87 -0
- package/dist/rest/handlers/move-handler.js.map +1 -0
- package/dist/rest/handlers/reply-handler.d.ts +17 -0
- package/dist/rest/handlers/reply-handler.js +93 -0
- package/dist/rest/handlers/reply-handler.js.map +1 -0
- package/dist/rest/handlers/search-handler.d.ts +20 -0
- package/dist/rest/handlers/search-handler.js +92 -0
- package/dist/rest/handlers/search-handler.js.map +1 -0
- package/dist/rest/handlers/send-handler.d.ts +18 -0
- package/dist/rest/handlers/send-handler.js +89 -0
- package/dist/rest/handlers/send-handler.js.map +1 -0
- package/dist/rest/handlers/thread-handler.d.ts +17 -0
- package/dist/rest/handlers/thread-handler.js +87 -0
- package/dist/rest/handlers/thread-handler.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { defineCommand } from '@kb-labs/sdk';
|
|
2
|
+
import { resolveAccount, searchMessages, InboxError } from '@kb-labs/inbox-core';
|
|
3
|
+
|
|
4
|
+
// src/commands/mail-search.ts
|
|
5
|
+
function getHint(code, message) {
|
|
6
|
+
switch (code) {
|
|
7
|
+
case "ENV_MISSING":
|
|
8
|
+
return "Set INBOX_ACCOUNTS=work,personal and account vars (INBOX_ACCOUNT_WORK_USER, _PASS, _IMAP_HOST, _SMTP_HOST)";
|
|
9
|
+
case "ACCOUNT_NOT_FOUND":
|
|
10
|
+
return `Use kb inbox accounts to see configured accounts, then pass the name with --account`;
|
|
11
|
+
case "IMAP_AUTH_FAILED":
|
|
12
|
+
return "Create an app-password in Yandex ID settings (yandex.ru/id) and set it as INBOX_ACCOUNT_<NAME>_PASS";
|
|
13
|
+
case "IMAP_CONNECT_FAILED":
|
|
14
|
+
return `Check INBOX_ACCOUNT_<NAME>_IMAP_HOST and _IMAP_PORT, and that the server is reachable`;
|
|
15
|
+
case "IMAP_TIMEOUT":
|
|
16
|
+
return "Server did not respond in time \u2014 try again in a moment";
|
|
17
|
+
case "IMAP_MAILBOX_NOT_FOUND":
|
|
18
|
+
return "Run kb inbox folders --account <name> to see available folders";
|
|
19
|
+
case "MESSAGE_NOT_FOUND":
|
|
20
|
+
return "The uid may be stale \u2014 refresh with: kb inbox list";
|
|
21
|
+
case "SMTP_AUTH_FAILED":
|
|
22
|
+
return "Create an app-password in Yandex ID settings and set it as INBOX_ACCOUNT_<NAME>_PASS";
|
|
23
|
+
case "SMTP_CONNECT_FAILED":
|
|
24
|
+
return "Check INBOX_ACCOUNT_<NAME>_SMTP_HOST and _SMTP_PORT";
|
|
25
|
+
case "SMTP_INVALID_RECIPIENT":
|
|
26
|
+
return "Verify the --to address is a valid email";
|
|
27
|
+
case "VALIDATION_ERROR":
|
|
28
|
+
return void 0;
|
|
29
|
+
// message itself is descriptive enough
|
|
30
|
+
case "INTERNAL_ERROR":
|
|
31
|
+
return void 0;
|
|
32
|
+
default: {
|
|
33
|
+
return void 0;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function handleError(ctx, err, isJson) {
|
|
38
|
+
if (err instanceof InboxError) {
|
|
39
|
+
const hint = getHint(err.code, err.message);
|
|
40
|
+
if (isJson) {
|
|
41
|
+
ctx.ui?.json?.({
|
|
42
|
+
ok: false,
|
|
43
|
+
error: {
|
|
44
|
+
code: err.code,
|
|
45
|
+
message: err.message,
|
|
46
|
+
...hint && { hint }
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
} else {
|
|
50
|
+
ctx.ui?.error?.(err.message, {
|
|
51
|
+
...hint && { hint },
|
|
52
|
+
cause: `InboxError [${err.code}]`
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
58
|
+
if (isJson) {
|
|
59
|
+
ctx.ui?.json?.({
|
|
60
|
+
ok: false,
|
|
61
|
+
error: { code: "INTERNAL_ERROR", message }
|
|
62
|
+
});
|
|
63
|
+
} else {
|
|
64
|
+
ctx.ui?.error?.(message);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/utils/slim.ts
|
|
69
|
+
function formatDate(iso) {
|
|
70
|
+
const d = new Date(iso);
|
|
71
|
+
const now = /* @__PURE__ */ new Date();
|
|
72
|
+
const diff = now.getTime() - d.getTime();
|
|
73
|
+
if (diff < 6e4) {
|
|
74
|
+
return "just now";
|
|
75
|
+
}
|
|
76
|
+
if (diff < 36e5) {
|
|
77
|
+
return `${Math.floor(diff / 6e4)}m ago`;
|
|
78
|
+
}
|
|
79
|
+
if (diff < 864e5) {
|
|
80
|
+
return `${Math.floor(diff / 36e5)}h ago`;
|
|
81
|
+
}
|
|
82
|
+
if (diff < 7 * 864e5) {
|
|
83
|
+
return `${Math.floor(diff / 864e5)}d ago`;
|
|
84
|
+
}
|
|
85
|
+
return d.toLocaleDateString();
|
|
86
|
+
}
|
|
87
|
+
function formatFrom(from) {
|
|
88
|
+
return from.name ? `${from.name} <${from.address}>` : from.address;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/commands/mail-search.ts
|
|
92
|
+
var mail_search_default = defineCommand({
|
|
93
|
+
id: "inbox:mail.search",
|
|
94
|
+
description: "Search emails by from, subject, body, or text",
|
|
95
|
+
handler: {
|
|
96
|
+
async execute(ctx, input) {
|
|
97
|
+
try {
|
|
98
|
+
const account = resolveAccount(input.flags.account);
|
|
99
|
+
const since = input.flags.since ? parseSince(input.flags.since) : void 0;
|
|
100
|
+
const messages = await searchMessages(account, {
|
|
101
|
+
from: input.flags.from,
|
|
102
|
+
subject: input.flags.subject,
|
|
103
|
+
body: input.flags.body,
|
|
104
|
+
text: input.flags.text,
|
|
105
|
+
folder: input.flags.folder,
|
|
106
|
+
since,
|
|
107
|
+
limit: input.flags.limit ?? 20
|
|
108
|
+
});
|
|
109
|
+
if (input.flags.json) {
|
|
110
|
+
ctx.ui?.json?.({ ok: true, result: messages });
|
|
111
|
+
return { exitCode: 0, result: messages };
|
|
112
|
+
}
|
|
113
|
+
if (!messages.length) {
|
|
114
|
+
ctx.ui?.info?.("No messages found.");
|
|
115
|
+
return { exitCode: 0, result: messages };
|
|
116
|
+
}
|
|
117
|
+
ctx.ui?.chain?.(messages.map((msg) => ({
|
|
118
|
+
title: `[${msg.uid}] ${msg.unread ? "\u25CF " : ""}${msg.subject}`,
|
|
119
|
+
sections: [{
|
|
120
|
+
items: [
|
|
121
|
+
`from: ${formatFrom(msg.from)} ${formatDate(msg.date)} folder: ${msg.folder}`
|
|
122
|
+
]
|
|
123
|
+
}]
|
|
124
|
+
})));
|
|
125
|
+
return { exitCode: 0, result: messages };
|
|
126
|
+
} catch (err) {
|
|
127
|
+
handleError(ctx, err, input.flags.json);
|
|
128
|
+
return { exitCode: 1, result: null };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
function parseSince(raw) {
|
|
134
|
+
const match = raw.match(/^(\d+)(h|d|w)$/i);
|
|
135
|
+
if (match) {
|
|
136
|
+
const n = parseInt(match[1] ?? "0", 10);
|
|
137
|
+
const unit = (match[2] ?? "h").toLowerCase();
|
|
138
|
+
const ms = unit === "h" ? n * 36e5 : unit === "d" ? n * 864e5 : n * 7 * 864e5;
|
|
139
|
+
return new Date(Date.now() - ms);
|
|
140
|
+
}
|
|
141
|
+
return new Date(raw);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export { mail_search_default as default };
|
|
145
|
+
//# sourceMappingURL=mail-search.js.map
|
|
146
|
+
//# sourceMappingURL=mail-search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/error.ts","../../src/utils/slim.ts","../../src/commands/mail-search.ts"],"names":[],"mappings":"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","file":"mail-search.js","sourcesContent":["import type { PluginContextV3 } from '@kb-labs/sdk';\nimport { InboxError, type InboxErrorCode } from '@kb-labs/inbox-core';\n\n// HTTP status codes for REST layer\nconst REST_STATUS: Record<InboxErrorCode, number> = {\n ENV_MISSING: 400,\n ACCOUNT_NOT_FOUND: 400,\n IMAP_AUTH_FAILED: 401,\n IMAP_CONNECT_FAILED: 503,\n IMAP_TIMEOUT: 504,\n IMAP_MAILBOX_NOT_FOUND: 404,\n MESSAGE_NOT_FOUND: 404,\n SMTP_AUTH_FAILED: 401,\n SMTP_CONNECT_FAILED: 503,\n SMTP_INVALID_RECIPIENT: 400,\n VALIDATION_ERROR: 400,\n INTERNAL_ERROR: 500,\n};\n\n// Human-readable hints per error code\nfunction getHint(code: InboxErrorCode, message: string): string | undefined {\n switch (code) {\n case 'ENV_MISSING':\n return 'Set INBOX_ACCOUNTS=work,personal and account vars (INBOX_ACCOUNT_WORK_USER, _PASS, _IMAP_HOST, _SMTP_HOST)';\n case 'ACCOUNT_NOT_FOUND':\n return `Use kb inbox accounts to see configured accounts, then pass the name with --account`;\n case 'IMAP_AUTH_FAILED':\n return 'Create an app-password in Yandex ID settings (yandex.ru/id) and set it as INBOX_ACCOUNT_<NAME>_PASS';\n case 'IMAP_CONNECT_FAILED':\n return `Check INBOX_ACCOUNT_<NAME>_IMAP_HOST and _IMAP_PORT, and that the server is reachable`;\n case 'IMAP_TIMEOUT':\n return 'Server did not respond in time — try again in a moment';\n case 'IMAP_MAILBOX_NOT_FOUND':\n return 'Run kb inbox folders --account <name> to see available folders';\n case 'MESSAGE_NOT_FOUND':\n return 'The uid may be stale — refresh with: kb inbox list';\n case 'SMTP_AUTH_FAILED':\n return 'Create an app-password in Yandex ID settings and set it as INBOX_ACCOUNT_<NAME>_PASS';\n case 'SMTP_CONNECT_FAILED':\n return 'Check INBOX_ACCOUNT_<NAME>_SMTP_HOST and _SMTP_PORT';\n case 'SMTP_INVALID_RECIPIENT':\n return 'Verify the --to address is a valid email';\n case 'VALIDATION_ERROR':\n return undefined; // message itself is descriptive enough\n case 'INTERNAL_ERROR':\n return undefined;\n default: {\n const _: never = code;\n void _;\n return undefined;\n }\n }\n}\n\n/**\n * Output a structured error for CLI commands.\n *\n * JSON mode → { ok: false, error: { code, message, hint? } } — agent-parseable\n * Human mode → ctx.ui.error() with hint and cause fields\n *\n * Call right before `return { exitCode: 1, result: null }`.\n */\nexport function handleError(ctx: PluginContextV3, err: unknown, isJson?: boolean): void {\n if (err instanceof InboxError) {\n const hint = getHint(err.code, err.message);\n\n if (isJson) {\n ctx.ui?.json?.({\n ok: false,\n error: {\n code: err.code,\n message: err.message,\n ...(hint && { hint }),\n },\n });\n } else {\n ctx.ui?.error?.(err.message, {\n ...(hint && { hint }),\n cause: `InboxError [${err.code}]`,\n });\n }\n return;\n }\n\n // Unknown error\n const message = err instanceof Error ? err.message : String(err);\n\n if (isJson) {\n ctx.ui?.json?.({\n ok: false,\n error: { code: 'INTERNAL_ERROR', message },\n });\n } else {\n ctx.ui?.error?.(message);\n }\n}\n\n/**\n * Re-throws err as an HTTP-aware error for REST handlers.\n * InboxError → preserves HTTP status; others → 500.\n */\nexport function rethrowForRest(err: unknown): never {\n if (err instanceof InboxError) {\n const hint = getHint(err.code, err.message);\n throw Object.assign(new Error(err.message), {\n statusCode: REST_STATUS[err.code] ?? 500,\n code: err.code,\n details: hint ? { hint } : undefined,\n });\n }\n\n const message = err instanceof Error ? err.message : String(err);\n throw Object.assign(new Error(message), {\n statusCode: 500,\n code: 'INTERNAL_ERROR',\n });\n}\n","import type { Email, EmailSlim } from '@kb-labs/inbox-contracts';\n\nexport function slimEmail(email: Email): EmailSlim {\n return {\n uid: email.uid,\n subject: email.subject,\n from: email.from,\n date: email.date,\n folder: email.folder,\n unread: !email.flags.includes('\\\\Seen'),\n flagged: email.flags.includes('\\\\Flagged'),\n hasAttachments: (email.attachments?.length ?? 0) > 0,\n };\n}\n\nexport function formatDate(iso: string): string {\n const d = new Date(iso);\n const now = new Date();\n const diff = now.getTime() - d.getTime();\n\n if (diff < 60_000) { return 'just now'; }\n if (diff < 3_600_000) { return `${Math.floor(diff / 60_000)}m ago`; }\n if (diff < 86_400_000) { return `${Math.floor(diff / 3_600_000)}h ago`; }\n if (diff < 7 * 86_400_000) { return `${Math.floor(diff / 86_400_000)}d ago`; }\n\n return d.toLocaleDateString();\n}\n\nexport function formatFrom(from: { name?: string; address: string }): string {\n return from.name ? `${from.name} <${from.address}>` : from.address;\n}\n","import { defineCommand, type CLIInput, type PluginContextV3 } from '@kb-labs/sdk';\nimport { resolveAccount, searchMessages } from '@kb-labs/inbox-core';\nimport { handleError } from '../utils/error.js';\nimport { formatDate, formatFrom } from '../utils/slim.js';\n\ntype SearchFlags = {\n from?: string;\n subject?: string;\n body?: string;\n text?: string;\n folder?: string;\n since?: string;\n limit?: number;\n account?: string;\n json?: boolean;\n full?: boolean;\n};\n\nexport default defineCommand({\n id: 'inbox:mail.search',\n description: 'Search emails by from, subject, body, or text',\n\n handler: {\n async execute(ctx: PluginContextV3, input: CLIInput<SearchFlags>) {\n try {\n const account = resolveAccount(input.flags.account);\n const since = input.flags.since ? parseSince(input.flags.since) : undefined;\n\n const messages = await searchMessages(account, {\n from: input.flags.from,\n subject: input.flags.subject,\n body: input.flags.body,\n text: input.flags.text,\n folder: input.flags.folder,\n since,\n limit: input.flags.limit ?? 20,\n });\n\n if (input.flags.json) {\n ctx.ui?.json?.({ ok: true, result: messages });\n return { exitCode: 0, result: messages };\n }\n\n if (!messages.length) {\n ctx.ui?.info?.('No messages found.');\n return { exitCode: 0, result: messages };\n }\n\n ctx.ui?.chain?.(messages.map(msg => ({\n title: `[${msg.uid}] ${msg.unread ? '● ' : ''}${msg.subject}`,\n sections: [{\n items: [\n `from: ${formatFrom(msg.from)} ${formatDate(msg.date)} folder: ${msg.folder}`,\n ],\n }],\n })));\n\n return { exitCode: 0, result: messages };\n } catch (err) {\n handleError(ctx, err, input.flags.json);\n return { exitCode: 1, result: null };\n }\n },\n },\n});\n\nfunction parseSince(raw: string): Date {\n const match = raw.match(/^(\\d+)(h|d|w)$/i);\n if (match) {\n const n = parseInt(match[1] ?? '0', 10);\n const unit = (match[2] ?? 'h').toLowerCase();\n const ms = unit === 'h' ? n * 3_600_000 : unit === 'd' ? n * 86_400_000 : n * 7 * 86_400_000;\n return new Date(Date.now() - ms);\n }\n return new Date(raw);\n}\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as _kb_labs_shared_command_kit from '@kb-labs/shared-command-kit';
|
|
2
|
+
import { CLIInput } from '@kb-labs/sdk';
|
|
3
|
+
|
|
4
|
+
type SendFlags = {
|
|
5
|
+
to?: string;
|
|
6
|
+
subject?: string;
|
|
7
|
+
body?: string;
|
|
8
|
+
cc?: string;
|
|
9
|
+
bcc?: string;
|
|
10
|
+
account?: string;
|
|
11
|
+
json?: boolean;
|
|
12
|
+
};
|
|
13
|
+
declare const _default: _kb_labs_shared_command_kit.CommandHandlerV3<unknown, CLIInput<SendFlags>, {
|
|
14
|
+
messageId: string;
|
|
15
|
+
} | null>;
|
|
16
|
+
|
|
17
|
+
export { _default as default };
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { defineCommand, validationError } from '@kb-labs/sdk';
|
|
2
|
+
import { resolveAccount, sendMessage, InboxError } from '@kb-labs/inbox-core';
|
|
3
|
+
|
|
4
|
+
// src/commands/mail-send.ts
|
|
5
|
+
function getHint(code, message) {
|
|
6
|
+
switch (code) {
|
|
7
|
+
case "ENV_MISSING":
|
|
8
|
+
return "Set INBOX_ACCOUNTS=work,personal and account vars (INBOX_ACCOUNT_WORK_USER, _PASS, _IMAP_HOST, _SMTP_HOST)";
|
|
9
|
+
case "ACCOUNT_NOT_FOUND":
|
|
10
|
+
return `Use kb inbox accounts to see configured accounts, then pass the name with --account`;
|
|
11
|
+
case "IMAP_AUTH_FAILED":
|
|
12
|
+
return "Create an app-password in Yandex ID settings (yandex.ru/id) and set it as INBOX_ACCOUNT_<NAME>_PASS";
|
|
13
|
+
case "IMAP_CONNECT_FAILED":
|
|
14
|
+
return `Check INBOX_ACCOUNT_<NAME>_IMAP_HOST and _IMAP_PORT, and that the server is reachable`;
|
|
15
|
+
case "IMAP_TIMEOUT":
|
|
16
|
+
return "Server did not respond in time \u2014 try again in a moment";
|
|
17
|
+
case "IMAP_MAILBOX_NOT_FOUND":
|
|
18
|
+
return "Run kb inbox folders --account <name> to see available folders";
|
|
19
|
+
case "MESSAGE_NOT_FOUND":
|
|
20
|
+
return "The uid may be stale \u2014 refresh with: kb inbox list";
|
|
21
|
+
case "SMTP_AUTH_FAILED":
|
|
22
|
+
return "Create an app-password in Yandex ID settings and set it as INBOX_ACCOUNT_<NAME>_PASS";
|
|
23
|
+
case "SMTP_CONNECT_FAILED":
|
|
24
|
+
return "Check INBOX_ACCOUNT_<NAME>_SMTP_HOST and _SMTP_PORT";
|
|
25
|
+
case "SMTP_INVALID_RECIPIENT":
|
|
26
|
+
return "Verify the --to address is a valid email";
|
|
27
|
+
case "VALIDATION_ERROR":
|
|
28
|
+
return void 0;
|
|
29
|
+
// message itself is descriptive enough
|
|
30
|
+
case "INTERNAL_ERROR":
|
|
31
|
+
return void 0;
|
|
32
|
+
default: {
|
|
33
|
+
return void 0;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function handleError(ctx, err, isJson) {
|
|
38
|
+
if (err instanceof InboxError) {
|
|
39
|
+
const hint = getHint(err.code, err.message);
|
|
40
|
+
if (isJson) {
|
|
41
|
+
ctx.ui?.json?.({
|
|
42
|
+
ok: false,
|
|
43
|
+
error: {
|
|
44
|
+
code: err.code,
|
|
45
|
+
message: err.message,
|
|
46
|
+
...hint && { hint }
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
} else {
|
|
50
|
+
ctx.ui?.error?.(err.message, {
|
|
51
|
+
...hint && { hint },
|
|
52
|
+
cause: `InboxError [${err.code}]`
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
58
|
+
if (isJson) {
|
|
59
|
+
ctx.ui?.json?.({
|
|
60
|
+
ok: false,
|
|
61
|
+
error: { code: "INTERNAL_ERROR", message }
|
|
62
|
+
});
|
|
63
|
+
} else {
|
|
64
|
+
ctx.ui?.error?.(message);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/commands/mail-send.ts
|
|
69
|
+
var mail_send_default = defineCommand({
|
|
70
|
+
id: "inbox:mail.send",
|
|
71
|
+
description: "Send an email",
|
|
72
|
+
handler: {
|
|
73
|
+
async execute(ctx, input) {
|
|
74
|
+
if (!input.flags.to) {
|
|
75
|
+
validationError(ctx, "--to is required", 'Usage: kb inbox send --to user@example.com --subject "..." --body "..."', input.flags.json);
|
|
76
|
+
return { exitCode: 1, result: null };
|
|
77
|
+
}
|
|
78
|
+
if (!input.flags.subject) {
|
|
79
|
+
validationError(ctx, "--subject is required", void 0, input.flags.json);
|
|
80
|
+
return { exitCode: 1, result: null };
|
|
81
|
+
}
|
|
82
|
+
if (!input.flags.body) {
|
|
83
|
+
validationError(ctx, "--body is required", void 0, input.flags.json);
|
|
84
|
+
return { exitCode: 1, result: null };
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const account = resolveAccount(input.flags.account);
|
|
88
|
+
const result = await sendMessage(account, {
|
|
89
|
+
to: input.flags.to,
|
|
90
|
+
subject: input.flags.subject,
|
|
91
|
+
body: input.flags.body,
|
|
92
|
+
cc: input.flags.cc,
|
|
93
|
+
bcc: input.flags.bcc
|
|
94
|
+
});
|
|
95
|
+
if (input.flags.json) {
|
|
96
|
+
ctx.ui?.json?.({ ok: true, result });
|
|
97
|
+
return { exitCode: 0, result };
|
|
98
|
+
}
|
|
99
|
+
ctx.ui?.success?.(`Message sent id: ${result.messageId}`);
|
|
100
|
+
return { exitCode: 0, result };
|
|
101
|
+
} catch (err) {
|
|
102
|
+
handleError(ctx, err, input.flags.json);
|
|
103
|
+
return { exitCode: 1, result: null };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
export { mail_send_default as default };
|
|
110
|
+
//# sourceMappingURL=mail-send.js.map
|
|
111
|
+
//# sourceMappingURL=mail-send.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/error.ts","../../src/commands/mail-send.ts"],"names":[],"mappings":";;;;AAoBA,SAAS,OAAA,CAAQ,MAAsB,OAAA,EAAqC;AAC1E,EAAA,QAAQ,IAAA;AAAM,IACZ,KAAK,aAAA;AACH,MAAA,OAAO,4GAAA;AAAA,IACT,KAAK,mBAAA;AACH,MAAA,OAAO,CAAA,mFAAA,CAAA;AAAA,IACT,KAAK,kBAAA;AACH,MAAA,OAAO,qGAAA;AAAA,IACT,KAAK,qBAAA;AACH,MAAA,OAAO,CAAA,qFAAA,CAAA;AAAA,IACT,KAAK,cAAA;AACH,MAAA,OAAO,6DAAA;AAAA,IACT,KAAK,wBAAA;AACH,MAAA,OAAO,gEAAA;AAAA,IACT,KAAK,mBAAA;AACH,MAAA,OAAO,yDAAA;AAAA,IACT,KAAK,kBAAA;AACH,MAAA,OAAO,sFAAA;AAAA,IACT,KAAK,qBAAA;AACH,MAAA,OAAO,qDAAA;AAAA,IACT,KAAK,wBAAA;AACH,MAAA,OAAO,0CAAA;AAAA,IACT,KAAK,kBAAA;AACH,MAAA,OAAO,MAAA;AAAA;AAAA,IACT,KAAK,gBAAA;AACH,MAAA,OAAO,MAAA;AAAA,IACT,SAAS;AAGP,MAAA,OAAO,MAAA;AAAA,IACT;AAAA;AAEJ;AAUO,SAAS,WAAA,CAAY,GAAA,EAAsB,GAAA,EAAc,MAAA,EAAwB;AACtF,EAAA,IAAI,eAAe,UAAA,EAAY;AAC7B,IAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,GAAA,CAAI,IAAA,EAAM,IAAI,OAAO,CAAA;AAE1C,IAAA,IAAI,MAAA,EAAQ;AACV,MAAA,GAAA,CAAI,IAAI,IAAA,GAAO;AAAA,QACb,EAAA,EAAI,KAAA;AAAA,QACJ,KAAA,EAAO;AAAA,UACL,MAAM,GAAA,CAAI,IAAA;AAAA,UACV,SAAS,GAAA,CAAI,OAAA;AAAA,UACb,GAAI,IAAA,IAAQ,EAAE,IAAA;AAAK;AACrB,OACD,CAAA;AAAA,IACH,CAAA,MAAO;AACL,MAAA,GAAA,CAAI,EAAA,EAAI,KAAA,GAAQ,GAAA,CAAI,OAAA,EAAS;AAAA,QAC3B,GAAI,IAAA,IAAQ,EAAE,IAAA,EAAK;AAAA,QACnB,KAAA,EAAO,CAAA,YAAA,EAAe,GAAA,CAAI,IAAI,CAAA,CAAA;AAAA,OAC/B,CAAA;AAAA,IACH;AACA,IAAA;AAAA,EACF;AAGA,EAAA,MAAM,UAAU,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,OAAO,GAAG,CAAA;AAE/D,EAAA,IAAI,MAAA,EAAQ;AACV,IAAA,GAAA,CAAI,IAAI,IAAA,GAAO;AAAA,MACb,EAAA,EAAI,KAAA;AAAA,MACJ,KAAA,EAAO,EAAE,IAAA,EAAM,gBAAA,EAAkB,OAAA;AAAQ,KAC1C,CAAA;AAAA,EACH,CAAA,MAAO;AACL,IAAA,GAAA,CAAI,EAAA,EAAI,QAAQ,OAAO,CAAA;AAAA,EACzB;AACF;;;ACjFA,IAAO,oBAAQ,aAAA,CAAc;AAAA,EAC3B,EAAA,EAAI,iBAAA;AAAA,EACJ,WAAA,EAAa,eAAA;AAAA,EAEb,OAAA,EAAS;AAAA,IACP,MAAM,OAAA,CAAQ,GAAA,EAAsB,KAAA,EAA4B;AAC9D,MAAA,IAAI,CAAC,KAAA,CAAM,KAAA,CAAM,EAAA,EAAI;AACnB,QAAA,eAAA,CAAgB,GAAA,EAAK,kBAAA,EAAoB,yEAAA,EAA2E,KAAA,CAAM,MAAM,IAAI,CAAA;AACpI,QAAA,OAAO,EAAE,QAAA,EAAU,CAAA,EAAG,MAAA,EAAQ,IAAA,EAAK;AAAA,MACrC;AACA,MAAA,IAAI,CAAC,KAAA,CAAM,KAAA,CAAM,OAAA,EAAS;AACxB,QAAA,eAAA,CAAgB,GAAA,EAAK,uBAAA,EAAyB,MAAA,EAAW,KAAA,CAAM,MAAM,IAAI,CAAA;AACzE,QAAA,OAAO,EAAE,QAAA,EAAU,CAAA,EAAG,MAAA,EAAQ,IAAA,EAAK;AAAA,MACrC;AACA,MAAA,IAAI,CAAC,KAAA,CAAM,KAAA,CAAM,IAAA,EAAM;AACrB,QAAA,eAAA,CAAgB,GAAA,EAAK,oBAAA,EAAsB,MAAA,EAAW,KAAA,CAAM,MAAM,IAAI,CAAA;AACtE,QAAA,OAAO,EAAE,QAAA,EAAU,CAAA,EAAG,MAAA,EAAQ,IAAA,EAAK;AAAA,MACrC;AAEA,MAAA,IAAI;AACF,QAAA,MAAM,OAAA,GAAU,cAAA,CAAe,KAAA,CAAM,KAAA,CAAM,OAAO,CAAA;AAClD,QAAA,MAAM,MAAA,GAAS,MAAM,WAAA,CAAY,OAAA,EAAS;AAAA,UACxC,EAAA,EAAI,MAAM,KAAA,CAAM,EAAA;AAAA,UAChB,OAAA,EAAS,MAAM,KAAA,CAAM,OAAA;AAAA,UACrB,IAAA,EAAM,MAAM,KAAA,CAAM,IAAA;AAAA,UAClB,EAAA,EAAI,MAAM,KAAA,CAAM,EAAA;AAAA,UAChB,GAAA,EAAK,MAAM,KAAA,CAAM;AAAA,SAClB,CAAA;AAED,QAAA,IAAI,KAAA,CAAM,MAAM,IAAA,EAAM;AACpB,UAAA,GAAA,CAAI,IAAI,IAAA,GAAO,EAAE,EAAA,EAAI,IAAA,EAAM,QAAQ,CAAA;AACnC,UAAA,OAAO,EAAE,QAAA,EAAU,CAAA,EAAG,MAAA,EAAO;AAAA,QAC/B;AAEA,QAAA,GAAA,CAAI,EAAA,EAAI,OAAA,GAAU,CAAA,kBAAA,EAAqB,MAAA,CAAO,SAAS,CAAA,CAAE,CAAA;AACzD,QAAA,OAAO,EAAE,QAAA,EAAU,CAAA,EAAG,MAAA,EAAO;AAAA,MAC/B,SAAS,GAAA,EAAK;AACZ,QAAA,WAAA,CAAY,GAAA,EAAK,GAAA,EAAK,KAAA,CAAM,KAAA,CAAM,IAAI,CAAA;AACtC,QAAA,OAAO,EAAE,QAAA,EAAU,CAAA,EAAG,MAAA,EAAQ,IAAA,EAAK;AAAA,MACrC;AAAA,IACF;AAAA;AAEJ,CAAC","file":"mail-send.js","sourcesContent":["import type { PluginContextV3 } from '@kb-labs/sdk';\nimport { InboxError, type InboxErrorCode } from '@kb-labs/inbox-core';\n\n// HTTP status codes for REST layer\nconst REST_STATUS: Record<InboxErrorCode, number> = {\n ENV_MISSING: 400,\n ACCOUNT_NOT_FOUND: 400,\n IMAP_AUTH_FAILED: 401,\n IMAP_CONNECT_FAILED: 503,\n IMAP_TIMEOUT: 504,\n IMAP_MAILBOX_NOT_FOUND: 404,\n MESSAGE_NOT_FOUND: 404,\n SMTP_AUTH_FAILED: 401,\n SMTP_CONNECT_FAILED: 503,\n SMTP_INVALID_RECIPIENT: 400,\n VALIDATION_ERROR: 400,\n INTERNAL_ERROR: 500,\n};\n\n// Human-readable hints per error code\nfunction getHint(code: InboxErrorCode, message: string): string | undefined {\n switch (code) {\n case 'ENV_MISSING':\n return 'Set INBOX_ACCOUNTS=work,personal and account vars (INBOX_ACCOUNT_WORK_USER, _PASS, _IMAP_HOST, _SMTP_HOST)';\n case 'ACCOUNT_NOT_FOUND':\n return `Use kb inbox accounts to see configured accounts, then pass the name with --account`;\n case 'IMAP_AUTH_FAILED':\n return 'Create an app-password in Yandex ID settings (yandex.ru/id) and set it as INBOX_ACCOUNT_<NAME>_PASS';\n case 'IMAP_CONNECT_FAILED':\n return `Check INBOX_ACCOUNT_<NAME>_IMAP_HOST and _IMAP_PORT, and that the server is reachable`;\n case 'IMAP_TIMEOUT':\n return 'Server did not respond in time — try again in a moment';\n case 'IMAP_MAILBOX_NOT_FOUND':\n return 'Run kb inbox folders --account <name> to see available folders';\n case 'MESSAGE_NOT_FOUND':\n return 'The uid may be stale — refresh with: kb inbox list';\n case 'SMTP_AUTH_FAILED':\n return 'Create an app-password in Yandex ID settings and set it as INBOX_ACCOUNT_<NAME>_PASS';\n case 'SMTP_CONNECT_FAILED':\n return 'Check INBOX_ACCOUNT_<NAME>_SMTP_HOST and _SMTP_PORT';\n case 'SMTP_INVALID_RECIPIENT':\n return 'Verify the --to address is a valid email';\n case 'VALIDATION_ERROR':\n return undefined; // message itself is descriptive enough\n case 'INTERNAL_ERROR':\n return undefined;\n default: {\n const _: never = code;\n void _;\n return undefined;\n }\n }\n}\n\n/**\n * Output a structured error for CLI commands.\n *\n * JSON mode → { ok: false, error: { code, message, hint? } } — agent-parseable\n * Human mode → ctx.ui.error() with hint and cause fields\n *\n * Call right before `return { exitCode: 1, result: null }`.\n */\nexport function handleError(ctx: PluginContextV3, err: unknown, isJson?: boolean): void {\n if (err instanceof InboxError) {\n const hint = getHint(err.code, err.message);\n\n if (isJson) {\n ctx.ui?.json?.({\n ok: false,\n error: {\n code: err.code,\n message: err.message,\n ...(hint && { hint }),\n },\n });\n } else {\n ctx.ui?.error?.(err.message, {\n ...(hint && { hint }),\n cause: `InboxError [${err.code}]`,\n });\n }\n return;\n }\n\n // Unknown error\n const message = err instanceof Error ? err.message : String(err);\n\n if (isJson) {\n ctx.ui?.json?.({\n ok: false,\n error: { code: 'INTERNAL_ERROR', message },\n });\n } else {\n ctx.ui?.error?.(message);\n }\n}\n\n/**\n * Re-throws err as an HTTP-aware error for REST handlers.\n * InboxError → preserves HTTP status; others → 500.\n */\nexport function rethrowForRest(err: unknown): never {\n if (err instanceof InboxError) {\n const hint = getHint(err.code, err.message);\n throw Object.assign(new Error(err.message), {\n statusCode: REST_STATUS[err.code] ?? 500,\n code: err.code,\n details: hint ? { hint } : undefined,\n });\n }\n\n const message = err instanceof Error ? err.message : String(err);\n throw Object.assign(new Error(message), {\n statusCode: 500,\n code: 'INTERNAL_ERROR',\n });\n}\n","import { defineCommand, validationError, type CLIInput, type PluginContextV3 } from '@kb-labs/sdk';\nimport { resolveAccount, sendMessage } from '@kb-labs/inbox-core';\nimport { handleError } from '../utils/error.js';\n\ntype SendFlags = {\n to?: string;\n subject?: string;\n body?: string;\n cc?: string;\n bcc?: string;\n account?: string;\n json?: boolean;\n};\n\nexport default defineCommand({\n id: 'inbox:mail.send',\n description: 'Send an email',\n\n handler: {\n async execute(ctx: PluginContextV3, input: CLIInput<SendFlags>) {\n if (!input.flags.to) {\n validationError(ctx, '--to is required', 'Usage: kb inbox send --to user@example.com --subject \"...\" --body \"...\"', input.flags.json);\n return { exitCode: 1, result: null };\n }\n if (!input.flags.subject) {\n validationError(ctx, '--subject is required', undefined, input.flags.json);\n return { exitCode: 1, result: null };\n }\n if (!input.flags.body) {\n validationError(ctx, '--body is required', undefined, input.flags.json);\n return { exitCode: 1, result: null };\n }\n\n try {\n const account = resolveAccount(input.flags.account);\n const result = await sendMessage(account, {\n to: input.flags.to,\n subject: input.flags.subject,\n body: input.flags.body,\n cc: input.flags.cc,\n bcc: input.flags.bcc,\n });\n\n if (input.flags.json) {\n ctx.ui?.json?.({ ok: true, result });\n return { exitCode: 0, result };\n }\n\n ctx.ui?.success?.(`Message sent id: ${result.messageId}`);\n return { exitCode: 0, result };\n } catch (err) {\n handleError(ctx, err, input.flags.json);\n return { exitCode: 1, result: null };\n }\n },\n },\n});\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as _kb_labs_shared_command_kit from '@kb-labs/shared-command-kit';
|
|
2
|
+
import * as _kb_labs_inbox_contracts from '@kb-labs/inbox-contracts';
|
|
3
|
+
import { CLIInput } from '@kb-labs/sdk';
|
|
4
|
+
|
|
5
|
+
type ThreadFlags = {
|
|
6
|
+
folder?: string;
|
|
7
|
+
account?: string;
|
|
8
|
+
json?: boolean;
|
|
9
|
+
full?: boolean;
|
|
10
|
+
};
|
|
11
|
+
declare const _default: _kb_labs_shared_command_kit.CommandHandlerV3<unknown, CLIInput<ThreadFlags>, _kb_labs_inbox_contracts.Email[] | null>;
|
|
12
|
+
|
|
13
|
+
export { _default as default };
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { defineCommand, validationError } from '@kb-labs/sdk';
|
|
2
|
+
import { resolveAccount, getThread, InboxError } from '@kb-labs/inbox-core';
|
|
3
|
+
|
|
4
|
+
// src/commands/mail-thread.ts
|
|
5
|
+
function getHint(code, message) {
|
|
6
|
+
switch (code) {
|
|
7
|
+
case "ENV_MISSING":
|
|
8
|
+
return "Set INBOX_ACCOUNTS=work,personal and account vars (INBOX_ACCOUNT_WORK_USER, _PASS, _IMAP_HOST, _SMTP_HOST)";
|
|
9
|
+
case "ACCOUNT_NOT_FOUND":
|
|
10
|
+
return `Use kb inbox accounts to see configured accounts, then pass the name with --account`;
|
|
11
|
+
case "IMAP_AUTH_FAILED":
|
|
12
|
+
return "Create an app-password in Yandex ID settings (yandex.ru/id) and set it as INBOX_ACCOUNT_<NAME>_PASS";
|
|
13
|
+
case "IMAP_CONNECT_FAILED":
|
|
14
|
+
return `Check INBOX_ACCOUNT_<NAME>_IMAP_HOST and _IMAP_PORT, and that the server is reachable`;
|
|
15
|
+
case "IMAP_TIMEOUT":
|
|
16
|
+
return "Server did not respond in time \u2014 try again in a moment";
|
|
17
|
+
case "IMAP_MAILBOX_NOT_FOUND":
|
|
18
|
+
return "Run kb inbox folders --account <name> to see available folders";
|
|
19
|
+
case "MESSAGE_NOT_FOUND":
|
|
20
|
+
return "The uid may be stale \u2014 refresh with: kb inbox list";
|
|
21
|
+
case "SMTP_AUTH_FAILED":
|
|
22
|
+
return "Create an app-password in Yandex ID settings and set it as INBOX_ACCOUNT_<NAME>_PASS";
|
|
23
|
+
case "SMTP_CONNECT_FAILED":
|
|
24
|
+
return "Check INBOX_ACCOUNT_<NAME>_SMTP_HOST and _SMTP_PORT";
|
|
25
|
+
case "SMTP_INVALID_RECIPIENT":
|
|
26
|
+
return "Verify the --to address is a valid email";
|
|
27
|
+
case "VALIDATION_ERROR":
|
|
28
|
+
return void 0;
|
|
29
|
+
// message itself is descriptive enough
|
|
30
|
+
case "INTERNAL_ERROR":
|
|
31
|
+
return void 0;
|
|
32
|
+
default: {
|
|
33
|
+
return void 0;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function handleError(ctx, err, isJson) {
|
|
38
|
+
if (err instanceof InboxError) {
|
|
39
|
+
const hint = getHint(err.code, err.message);
|
|
40
|
+
if (isJson) {
|
|
41
|
+
ctx.ui?.json?.({
|
|
42
|
+
ok: false,
|
|
43
|
+
error: {
|
|
44
|
+
code: err.code,
|
|
45
|
+
message: err.message,
|
|
46
|
+
...hint && { hint }
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
} else {
|
|
50
|
+
ctx.ui?.error?.(err.message, {
|
|
51
|
+
...hint && { hint },
|
|
52
|
+
cause: `InboxError [${err.code}]`
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
58
|
+
if (isJson) {
|
|
59
|
+
ctx.ui?.json?.({
|
|
60
|
+
ok: false,
|
|
61
|
+
error: { code: "INTERNAL_ERROR", message }
|
|
62
|
+
});
|
|
63
|
+
} else {
|
|
64
|
+
ctx.ui?.error?.(message);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/utils/slim.ts
|
|
69
|
+
function formatDate(iso) {
|
|
70
|
+
const d = new Date(iso);
|
|
71
|
+
const now = /* @__PURE__ */ new Date();
|
|
72
|
+
const diff = now.getTime() - d.getTime();
|
|
73
|
+
if (diff < 6e4) {
|
|
74
|
+
return "just now";
|
|
75
|
+
}
|
|
76
|
+
if (diff < 36e5) {
|
|
77
|
+
return `${Math.floor(diff / 6e4)}m ago`;
|
|
78
|
+
}
|
|
79
|
+
if (diff < 864e5) {
|
|
80
|
+
return `${Math.floor(diff / 36e5)}h ago`;
|
|
81
|
+
}
|
|
82
|
+
if (diff < 7 * 864e5) {
|
|
83
|
+
return `${Math.floor(diff / 864e5)}d ago`;
|
|
84
|
+
}
|
|
85
|
+
return d.toLocaleDateString();
|
|
86
|
+
}
|
|
87
|
+
function formatFrom(from) {
|
|
88
|
+
return from.name ? `${from.name} <${from.address}>` : from.address;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/commands/mail-thread.ts
|
|
92
|
+
var mail_thread_default = defineCommand({
|
|
93
|
+
id: "inbox:mail.thread",
|
|
94
|
+
description: "Get full email thread by uid",
|
|
95
|
+
handler: {
|
|
96
|
+
async execute(ctx, input) {
|
|
97
|
+
const [rawUid] = input.argv;
|
|
98
|
+
if (!rawUid) {
|
|
99
|
+
validationError(ctx, "uid is required", "Usage: kb inbox thread <uid>", input.flags.json);
|
|
100
|
+
return { exitCode: 1, result: null };
|
|
101
|
+
}
|
|
102
|
+
const uid = parseInt(rawUid, 10);
|
|
103
|
+
if (isNaN(uid)) {
|
|
104
|
+
validationError(ctx, `Invalid uid: "${rawUid}" \u2014 must be a number`, void 0, input.flags.json);
|
|
105
|
+
return { exitCode: 1, result: null };
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const account = resolveAccount(input.flags.account);
|
|
109
|
+
const thread = await getThread(account, uid, {
|
|
110
|
+
folder: input.flags.folder ?? "INBOX"
|
|
111
|
+
});
|
|
112
|
+
if (input.flags.json) {
|
|
113
|
+
ctx.ui?.json?.({ ok: true, result: thread });
|
|
114
|
+
return { exitCode: 0, result: thread };
|
|
115
|
+
}
|
|
116
|
+
ctx.ui?.info?.(`Thread: ${thread.length} message${thread.length !== 1 ? "s" : ""}`);
|
|
117
|
+
ctx.ui?.chain?.(thread.map((msg, i) => ({
|
|
118
|
+
title: `[${i + 1}/${thread.length}] [uid:${msg.uid}] ${msg.subject}`,
|
|
119
|
+
sections: [{
|
|
120
|
+
items: [
|
|
121
|
+
`from: ${formatFrom(msg.from)} ${formatDate(msg.date)}`,
|
|
122
|
+
msg.text ? msg.text.slice(0, 500) : "(no text body)"
|
|
123
|
+
]
|
|
124
|
+
}]
|
|
125
|
+
})));
|
|
126
|
+
return { exitCode: 0, result: thread };
|
|
127
|
+
} catch (err) {
|
|
128
|
+
handleError(ctx, err, input.flags.json);
|
|
129
|
+
return { exitCode: 1, result: null };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
export { mail_thread_default as default };
|
|
136
|
+
//# sourceMappingURL=mail-thread.js.map
|
|
137
|
+
//# sourceMappingURL=mail-thread.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/error.ts","../../src/utils/slim.ts","../../src/commands/mail-thread.ts"],"names":[],"mappings":"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","file":"mail-thread.js","sourcesContent":["import type { PluginContextV3 } from '@kb-labs/sdk';\nimport { InboxError, type InboxErrorCode } from '@kb-labs/inbox-core';\n\n// HTTP status codes for REST layer\nconst REST_STATUS: Record<InboxErrorCode, number> = {\n ENV_MISSING: 400,\n ACCOUNT_NOT_FOUND: 400,\n IMAP_AUTH_FAILED: 401,\n IMAP_CONNECT_FAILED: 503,\n IMAP_TIMEOUT: 504,\n IMAP_MAILBOX_NOT_FOUND: 404,\n MESSAGE_NOT_FOUND: 404,\n SMTP_AUTH_FAILED: 401,\n SMTP_CONNECT_FAILED: 503,\n SMTP_INVALID_RECIPIENT: 400,\n VALIDATION_ERROR: 400,\n INTERNAL_ERROR: 500,\n};\n\n// Human-readable hints per error code\nfunction getHint(code: InboxErrorCode, message: string): string | undefined {\n switch (code) {\n case 'ENV_MISSING':\n return 'Set INBOX_ACCOUNTS=work,personal and account vars (INBOX_ACCOUNT_WORK_USER, _PASS, _IMAP_HOST, _SMTP_HOST)';\n case 'ACCOUNT_NOT_FOUND':\n return `Use kb inbox accounts to see configured accounts, then pass the name with --account`;\n case 'IMAP_AUTH_FAILED':\n return 'Create an app-password in Yandex ID settings (yandex.ru/id) and set it as INBOX_ACCOUNT_<NAME>_PASS';\n case 'IMAP_CONNECT_FAILED':\n return `Check INBOX_ACCOUNT_<NAME>_IMAP_HOST and _IMAP_PORT, and that the server is reachable`;\n case 'IMAP_TIMEOUT':\n return 'Server did not respond in time — try again in a moment';\n case 'IMAP_MAILBOX_NOT_FOUND':\n return 'Run kb inbox folders --account <name> to see available folders';\n case 'MESSAGE_NOT_FOUND':\n return 'The uid may be stale — refresh with: kb inbox list';\n case 'SMTP_AUTH_FAILED':\n return 'Create an app-password in Yandex ID settings and set it as INBOX_ACCOUNT_<NAME>_PASS';\n case 'SMTP_CONNECT_FAILED':\n return 'Check INBOX_ACCOUNT_<NAME>_SMTP_HOST and _SMTP_PORT';\n case 'SMTP_INVALID_RECIPIENT':\n return 'Verify the --to address is a valid email';\n case 'VALIDATION_ERROR':\n return undefined; // message itself is descriptive enough\n case 'INTERNAL_ERROR':\n return undefined;\n default: {\n const _: never = code;\n void _;\n return undefined;\n }\n }\n}\n\n/**\n * Output a structured error for CLI commands.\n *\n * JSON mode → { ok: false, error: { code, message, hint? } } — agent-parseable\n * Human mode → ctx.ui.error() with hint and cause fields\n *\n * Call right before `return { exitCode: 1, result: null }`.\n */\nexport function handleError(ctx: PluginContextV3, err: unknown, isJson?: boolean): void {\n if (err instanceof InboxError) {\n const hint = getHint(err.code, err.message);\n\n if (isJson) {\n ctx.ui?.json?.({\n ok: false,\n error: {\n code: err.code,\n message: err.message,\n ...(hint && { hint }),\n },\n });\n } else {\n ctx.ui?.error?.(err.message, {\n ...(hint && { hint }),\n cause: `InboxError [${err.code}]`,\n });\n }\n return;\n }\n\n // Unknown error\n const message = err instanceof Error ? err.message : String(err);\n\n if (isJson) {\n ctx.ui?.json?.({\n ok: false,\n error: { code: 'INTERNAL_ERROR', message },\n });\n } else {\n ctx.ui?.error?.(message);\n }\n}\n\n/**\n * Re-throws err as an HTTP-aware error for REST handlers.\n * InboxError → preserves HTTP status; others → 500.\n */\nexport function rethrowForRest(err: unknown): never {\n if (err instanceof InboxError) {\n const hint = getHint(err.code, err.message);\n throw Object.assign(new Error(err.message), {\n statusCode: REST_STATUS[err.code] ?? 500,\n code: err.code,\n details: hint ? { hint } : undefined,\n });\n }\n\n const message = err instanceof Error ? err.message : String(err);\n throw Object.assign(new Error(message), {\n statusCode: 500,\n code: 'INTERNAL_ERROR',\n });\n}\n","import type { Email, EmailSlim } from '@kb-labs/inbox-contracts';\n\nexport function slimEmail(email: Email): EmailSlim {\n return {\n uid: email.uid,\n subject: email.subject,\n from: email.from,\n date: email.date,\n folder: email.folder,\n unread: !email.flags.includes('\\\\Seen'),\n flagged: email.flags.includes('\\\\Flagged'),\n hasAttachments: (email.attachments?.length ?? 0) > 0,\n };\n}\n\nexport function formatDate(iso: string): string {\n const d = new Date(iso);\n const now = new Date();\n const diff = now.getTime() - d.getTime();\n\n if (diff < 60_000) { return 'just now'; }\n if (diff < 3_600_000) { return `${Math.floor(diff / 60_000)}m ago`; }\n if (diff < 86_400_000) { return `${Math.floor(diff / 3_600_000)}h ago`; }\n if (diff < 7 * 86_400_000) { return `${Math.floor(diff / 86_400_000)}d ago`; }\n\n return d.toLocaleDateString();\n}\n\nexport function formatFrom(from: { name?: string; address: string }): string {\n return from.name ? `${from.name} <${from.address}>` : from.address;\n}\n","import { defineCommand, validationError, type CLIInput, type PluginContextV3 } from '@kb-labs/sdk';\nimport { resolveAccount, getThread } from '@kb-labs/inbox-core';\nimport { handleError } from '../utils/error.js';\nimport { formatDate, formatFrom } from '../utils/slim.js';\n\ntype ThreadFlags = {\n folder?: string;\n account?: string;\n json?: boolean;\n full?: boolean;\n};\n\nexport default defineCommand({\n id: 'inbox:mail.thread',\n description: 'Get full email thread by uid',\n\n handler: {\n async execute(ctx: PluginContextV3, input: CLIInput<ThreadFlags>) {\n const [rawUid] = input.argv;\n if (!rawUid) {\n validationError(ctx, 'uid is required', 'Usage: kb inbox thread <uid>', input.flags.json);\n return { exitCode: 1, result: null };\n }\n\n const uid = parseInt(rawUid, 10);\n if (isNaN(uid)) {\n validationError(ctx, `Invalid uid: \"${rawUid}\" — must be a number`, undefined, input.flags.json);\n return { exitCode: 1, result: null };\n }\n\n try {\n const account = resolveAccount(input.flags.account);\n const thread = await getThread(account, uid, {\n folder: input.flags.folder ?? 'INBOX',\n });\n\n if (input.flags.json) {\n ctx.ui?.json?.({ ok: true, result: thread });\n return { exitCode: 0, result: thread };\n }\n\n ctx.ui?.info?.(`Thread: ${thread.length} message${thread.length !== 1 ? 's' : ''}`);\n\n ctx.ui?.chain?.(thread.map((msg, i) => ({\n title: `[${i + 1}/${thread.length}] [uid:${msg.uid}] ${msg.subject}`,\n sections: [{\n items: [\n `from: ${formatFrom(msg.from)} ${formatDate(msg.date)}`,\n msg.text ? msg.text.slice(0, 500) : '(no text body)',\n ],\n }],\n })));\n\n return { exitCode: 0, result: thread };\n } catch (err) {\n handleError(ctx, err, input.flags.json);\n return { exitCode: 1, result: null };\n }\n },\n },\n});\n"]}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { combinePermissions, GET, POST, PATCH, DELETE, mergeCliGroups, group, cmd } from '@kb-labs/sdk';
|
|
2
|
+
import { INBOX_ROUTES, INBOX_BASE_PATH } from '@kb-labs/inbox-contracts';
|
|
3
|
+
|
|
4
|
+
// src/manifest.ts
|
|
5
|
+
var permissions = combinePermissions().withEnv([
|
|
6
|
+
"INBOX_ACCOUNTS",
|
|
7
|
+
"INBOX_ACCOUNT_*"
|
|
8
|
+
// covers all account vars: _USER, _PASS, _IMAP_HOST, etc.
|
|
9
|
+
]).withNetwork({
|
|
10
|
+
tcp: { connect: ["imap.yandex.ru:993", "smtp.yandex.ru:465", "smtp.yandex.ru:587"] }
|
|
11
|
+
}).withQuotas({ timeoutMs: 3e4, memoryMb: 128 }).build();
|
|
12
|
+
var manifest = {
|
|
13
|
+
schema: "kb.plugin/3",
|
|
14
|
+
id: "@kb-labs/inbox",
|
|
15
|
+
version: "0.1.0",
|
|
16
|
+
display: {
|
|
17
|
+
name: "Inbox",
|
|
18
|
+
description: "Manage email via IMAP/SMTP \u2014 list, search, send, move, delete from CLI and REST API",
|
|
19
|
+
tags: ["email", "inbox", "imap", "smtp", "productivity"]
|
|
20
|
+
},
|
|
21
|
+
permissions,
|
|
22
|
+
cli: mergeCliGroups(
|
|
23
|
+
group({ path: "inbox", describe: "Email management", category: "Mail" }, [
|
|
24
|
+
cmd("inbox list", "./commands/mail-list.js#default", "List emails in a folder").read().flags({
|
|
25
|
+
folder: { type: "string", description: "Folder to list (default: INBOX)" },
|
|
26
|
+
unread: { type: "boolean", description: "Show only unread messages" },
|
|
27
|
+
since: { type: "string", description: "Filter by age: 24h, 7d, 2w or ISO date" },
|
|
28
|
+
limit: { type: "number", description: "Max results (default: 50)" },
|
|
29
|
+
account: { type: "string", description: "Account name or index (default: first)" },
|
|
30
|
+
json: { type: "boolean", description: "Output JSON" },
|
|
31
|
+
full: { type: "boolean", description: "Full JSON (requires --json)" }
|
|
32
|
+
}).examples(["kb inbox list", "kb inbox list --unread --since 24h", "kb inbox list --folder Work --account personal --json"]),
|
|
33
|
+
cmd("inbox get", "./commands/mail-get.js#default", "Get full email content by uid").read().flags({
|
|
34
|
+
folder: { type: "string", description: "Folder containing the message (default: INBOX)" },
|
|
35
|
+
attachments: { type: "boolean", description: "Include attachment list" },
|
|
36
|
+
account: { type: "string", description: "Account name or index" },
|
|
37
|
+
json: { type: "boolean", description: "Output JSON" },
|
|
38
|
+
full: { type: "boolean", description: "Full JSON (requires --json)" }
|
|
39
|
+
}).examples(["kb inbox get 42", "kb inbox get 42 --attachments --json", "kb inbox get 42 --folder Sent --account work"]),
|
|
40
|
+
cmd("inbox thread", "./commands/mail-thread.js#default", "Get full email thread by uid").read().flags({
|
|
41
|
+
folder: { type: "string", description: "Folder (default: INBOX)" },
|
|
42
|
+
account: { type: "string", description: "Account name or index" },
|
|
43
|
+
json: { type: "boolean", description: "Output JSON" },
|
|
44
|
+
full: { type: "boolean", description: "Full JSON (requires --json)" }
|
|
45
|
+
}).examples(["kb inbox thread 42", "kb inbox thread 42 --json"]),
|
|
46
|
+
cmd("inbox search", "./commands/mail-search.js#default", "Search emails by from, subject, body, or text").read().flags({
|
|
47
|
+
from: { type: "string", description: "Filter by sender" },
|
|
48
|
+
subject: { type: "string", description: "Filter by subject" },
|
|
49
|
+
body: { type: "string", description: "Filter by body text" },
|
|
50
|
+
text: { type: "string", description: "Full-text search (subject + body)" },
|
|
51
|
+
folder: { type: "string", description: "Folder to search (default: INBOX)" },
|
|
52
|
+
since: { type: "string", description: "Filter by age: 24h, 7d, 2w or ISO date" },
|
|
53
|
+
limit: { type: "number", description: "Max results (default: 20)" },
|
|
54
|
+
account: { type: "string", description: "Account name or index" },
|
|
55
|
+
json: { type: "boolean", description: "Output JSON" },
|
|
56
|
+
full: { type: "boolean", description: "Full JSON (requires --json)" }
|
|
57
|
+
}).examples([
|
|
58
|
+
"kb inbox search --from boss@company.com",
|
|
59
|
+
'kb inbox search --subject "invoice" --since 30d --json',
|
|
60
|
+
'kb inbox search --text "urgent" --limit 5 --json'
|
|
61
|
+
]),
|
|
62
|
+
cmd("inbox send", "./commands/mail-send.js#default", "Send an email").mutate().flags({
|
|
63
|
+
to: { type: "string", description: "Recipient address (required)" },
|
|
64
|
+
subject: { type: "string", description: "Subject (required)" },
|
|
65
|
+
body: { type: "string", description: "Message body (required)" },
|
|
66
|
+
cc: { type: "string", description: "CC address" },
|
|
67
|
+
bcc: { type: "string", description: "BCC address" },
|
|
68
|
+
account: { type: "string", description: "Account name or index" },
|
|
69
|
+
json: { type: "boolean", description: "Output JSON" }
|
|
70
|
+
}).examples([
|
|
71
|
+
'kb inbox send --to user@example.com --subject "Hello" --body "Hi there"',
|
|
72
|
+
'kb inbox send --to user@example.com --subject "Report" --body "..." --account work --json'
|
|
73
|
+
]),
|
|
74
|
+
cmd("inbox reply", "./commands/mail-reply.js#default", "Reply to an email by uid (preserves thread)").mutate().flags({
|
|
75
|
+
body: { type: "string", description: "Reply body (required)" },
|
|
76
|
+
folder: { type: "string", description: "Folder containing the original (default: INBOX)" },
|
|
77
|
+
account: { type: "string", description: "Account name or index" },
|
|
78
|
+
json: { type: "boolean", description: "Output JSON" }
|
|
79
|
+
}).examples(['kb inbox reply 42 --body "Thanks, will do"', 'kb inbox reply 42 --body "On it" --account work --json']),
|
|
80
|
+
cmd("inbox move", "./commands/mail-move.js#default", "Move an email to a folder").mutate().flags({
|
|
81
|
+
folder: { type: "string", description: "Destination folder (required)" },
|
|
82
|
+
from: { type: "string", description: "Source folder (default: INBOX)" },
|
|
83
|
+
account: { type: "string", description: "Account name or index" },
|
|
84
|
+
json: { type: "boolean", description: "Output JSON" }
|
|
85
|
+
}).examples(["kb inbox move 42 --folder Work", "kb inbox move 42 --folder Archive --from Sent --json"]),
|
|
86
|
+
cmd("inbox mark", "./commands/mail-mark.js#default", "Mark an email as read, unread, spam, or flagged").mutate().flags({
|
|
87
|
+
read: { type: "boolean", description: "Mark as read" },
|
|
88
|
+
unread: { type: "boolean", description: "Mark as unread" },
|
|
89
|
+
spam: { type: "boolean", description: "Mark as spam and move to Junk" },
|
|
90
|
+
flagged: { type: "boolean", description: "Flag the message" },
|
|
91
|
+
unflagged: { type: "boolean", description: "Remove flag" },
|
|
92
|
+
folder: { type: "string", description: "Folder (default: INBOX)" },
|
|
93
|
+
account: { type: "string", description: "Account name or index" },
|
|
94
|
+
json: { type: "boolean", description: "Output JSON" }
|
|
95
|
+
}).examples(["kb inbox mark 42 --read", "kb inbox mark 42 --spam", "kb inbox mark 42 --flagged --json"]),
|
|
96
|
+
cmd("inbox delete", "./commands/mail-delete.js#default", "Delete an email (moves to Trash)").mutate().flags({
|
|
97
|
+
folder: { type: "string", description: "Folder containing the message (default: INBOX)" },
|
|
98
|
+
account: { type: "string", description: "Account name or index" },
|
|
99
|
+
json: { type: "boolean", description: "Output JSON" }
|
|
100
|
+
}).examples(["kb inbox delete 42", "kb inbox delete 42 --folder Spam --json"]),
|
|
101
|
+
cmd("inbox folders", "./commands/mail-folders.js#default", "List available folders/mailboxes").read().flags({
|
|
102
|
+
account: { type: "string", description: "Account name or index" },
|
|
103
|
+
json: { type: "boolean", description: "Output JSON" }
|
|
104
|
+
}).examples(["kb inbox folders", "kb inbox folders --account work --json"]),
|
|
105
|
+
cmd("inbox accounts", "./commands/mail-accounts.js#default", "List configured email accounts").read().category("Config").flags({
|
|
106
|
+
json: { type: "boolean", description: "Output JSON" }
|
|
107
|
+
}).examples(["kb inbox accounts", "kb inbox accounts --json"])
|
|
108
|
+
]),
|
|
109
|
+
group({ path: "inbox mail", describe: "Mail operations", category: "Mail" }, [])
|
|
110
|
+
),
|
|
111
|
+
rest: {
|
|
112
|
+
basePath: INBOX_BASE_PATH,
|
|
113
|
+
routes: [
|
|
114
|
+
GET(INBOX_ROUTES.MESSAGES, "./rest/handlers/list-handler.js#default"),
|
|
115
|
+
GET(INBOX_ROUTES.MESSAGE, "./rest/handlers/get-handler.js#default"),
|
|
116
|
+
GET(INBOX_ROUTES.MESSAGE_THREAD, "./rest/handlers/thread-handler.js#default"),
|
|
117
|
+
GET(INBOX_ROUTES.SEARCH, "./rest/handlers/search-handler.js#default"),
|
|
118
|
+
POST(INBOX_ROUTES.MESSAGES, "./rest/handlers/send-handler.js#default", { input: { zod: "@kb-labs/inbox-contracts#SendMessageSchema" } }),
|
|
119
|
+
POST(INBOX_ROUTES.MESSAGE_REPLY, "./rest/handlers/reply-handler.js#default", { input: { zod: "@kb-labs/inbox-contracts#ReplyMessageSchema" } }),
|
|
120
|
+
PATCH(INBOX_ROUTES.MESSAGE_MOVE, "./rest/handlers/move-handler.js#default", { input: { zod: "@kb-labs/inbox-contracts#MoveMessageSchema" } }),
|
|
121
|
+
PATCH(INBOX_ROUTES.MESSAGE_MARK, "./rest/handlers/mark-handler.js#default", { input: { zod: "@kb-labs/inbox-contracts#MarkMessageSchema" } }),
|
|
122
|
+
DELETE(INBOX_ROUTES.MESSAGE, "./rest/handlers/delete-handler.js#default"),
|
|
123
|
+
GET(INBOX_ROUTES.FOLDERS, "./rest/handlers/folders-handler.js#default"),
|
|
124
|
+
GET(INBOX_ROUTES.ACCOUNTS, "./rest/handlers/accounts-handler.js#default")
|
|
125
|
+
]
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export { manifest as default };
|
|
130
|
+
//# sourceMappingURL=index.js.map
|
|
131
|
+
//# sourceMappingURL=index.js.map
|