@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.
Files changed (74) hide show
  1. package/README.md +5 -0
  2. package/dist/commands/mail-accounts.d.ts +10 -0
  3. package/dist/commands/mail-accounts.js +104 -0
  4. package/dist/commands/mail-accounts.js.map +1 -0
  5. package/dist/commands/mail-delete.d.ts +14 -0
  6. package/dist/commands/mail-delete.js +103 -0
  7. package/dist/commands/mail-delete.js.map +1 -0
  8. package/dist/commands/mail-folders.d.ts +11 -0
  9. package/dist/commands/mail-folders.js +103 -0
  10. package/dist/commands/mail-folders.js.map +1 -0
  11. package/dist/commands/mail-get.d.ts +14 -0
  12. package/dist/commands/mail-get.js +150 -0
  13. package/dist/commands/mail-get.js.map +1 -0
  14. package/dist/commands/mail-list.d.ts +16 -0
  15. package/dist/commands/mail-list.js +144 -0
  16. package/dist/commands/mail-list.js.map +1 -0
  17. package/dist/commands/mail-mark.d.ts +20 -0
  18. package/dist/commands/mail-mark.js +108 -0
  19. package/dist/commands/mail-mark.js.map +1 -0
  20. package/dist/commands/mail-move.d.ts +15 -0
  21. package/dist/commands/mail-move.js +107 -0
  22. package/dist/commands/mail-move.js.map +1 -0
  23. package/dist/commands/mail-reply.d.ts +14 -0
  24. package/dist/commands/mail-reply.js +114 -0
  25. package/dist/commands/mail-reply.js.map +1 -0
  26. package/dist/commands/mail-search.d.ts +19 -0
  27. package/dist/commands/mail-search.js +146 -0
  28. package/dist/commands/mail-search.js.map +1 -0
  29. package/dist/commands/mail-send.d.ts +17 -0
  30. package/dist/commands/mail-send.js +111 -0
  31. package/dist/commands/mail-send.js.map +1 -0
  32. package/dist/commands/mail-thread.d.ts +13 -0
  33. package/dist/commands/mail-thread.js +137 -0
  34. package/dist/commands/mail-thread.js.map +1 -0
  35. package/dist/index.d.ts +2 -0
  36. package/dist/index.js +131 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/manifest.d.ts +23 -0
  39. package/dist/manifest.js +132 -0
  40. package/dist/manifest.js.map +1 -0
  41. package/dist/rest/handlers/accounts-handler.d.ts +10 -0
  42. package/dist/rest/handlers/accounts-handler.js +81 -0
  43. package/dist/rest/handlers/accounts-handler.js.map +1 -0
  44. package/dist/rest/handlers/delete-handler.d.ts +19 -0
  45. package/dist/rest/handlers/delete-handler.js +87 -0
  46. package/dist/rest/handlers/delete-handler.js.map +1 -0
  47. package/dist/rest/handlers/folders-handler.d.ts +13 -0
  48. package/dist/rest/handlers/folders-handler.js +82 -0
  49. package/dist/rest/handlers/folders-handler.js.map +1 -0
  50. package/dist/rest/handlers/get-handler.d.ts +18 -0
  51. package/dist/rest/handlers/get-handler.js +90 -0
  52. package/dist/rest/handlers/get-handler.js.map +1 -0
  53. package/dist/rest/handlers/list-handler.d.ts +17 -0
  54. package/dist/rest/handlers/list-handler.js +89 -0
  55. package/dist/rest/handlers/list-handler.js.map +1 -0
  56. package/dist/rest/handlers/mark-handler.d.ts +18 -0
  57. package/dist/rest/handlers/mark-handler.js +87 -0
  58. package/dist/rest/handlers/mark-handler.js.map +1 -0
  59. package/dist/rest/handlers/move-handler.d.ts +18 -0
  60. package/dist/rest/handlers/move-handler.js +87 -0
  61. package/dist/rest/handlers/move-handler.js.map +1 -0
  62. package/dist/rest/handlers/reply-handler.d.ts +17 -0
  63. package/dist/rest/handlers/reply-handler.js +93 -0
  64. package/dist/rest/handlers/reply-handler.js.map +1 -0
  65. package/dist/rest/handlers/search-handler.d.ts +20 -0
  66. package/dist/rest/handlers/search-handler.js +92 -0
  67. package/dist/rest/handlers/search-handler.js.map +1 -0
  68. package/dist/rest/handlers/send-handler.d.ts +18 -0
  69. package/dist/rest/handlers/send-handler.js +89 -0
  70. package/dist/rest/handlers/send-handler.js.map +1 -0
  71. package/dist/rest/handlers/thread-handler.d.ts +17 -0
  72. package/dist/rest/handlers/thread-handler.js +87 -0
  73. package/dist/rest/handlers/thread-handler.js.map +1 -0
  74. package/package.json +54 -0
@@ -0,0 +1,144 @@
1
+ import { defineCommand } from '@kb-labs/sdk';
2
+ import { resolveAccount, listMessages, InboxError } from '@kb-labs/inbox-core';
3
+
4
+ // src/commands/mail-list.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-list.ts
92
+ var mail_list_default = defineCommand({
93
+ id: "inbox:mail.list",
94
+ description: "List emails in a folder",
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 listMessages(account, {
101
+ folder: input.flags.folder ?? "INBOX",
102
+ unreadOnly: input.flags.unread,
103
+ since,
104
+ limit: input.flags.limit ?? 50
105
+ });
106
+ if (input.flags.json) {
107
+ ctx.ui?.json?.({ ok: true, result: messages });
108
+ return { exitCode: 0, result: messages };
109
+ }
110
+ if (!messages.length) {
111
+ ctx.ui?.info?.("No messages found.");
112
+ return { exitCode: 0, result: messages };
113
+ }
114
+ ctx.ui?.chain?.(messages.map((msg) => ({
115
+ title: `[${msg.uid}] ${msg.unread ? "\u25CF " : ""}${msg.subject}`,
116
+ sections: [{
117
+ items: [
118
+ `from: ${formatFrom(msg.from)} ${formatDate(msg.date)}`,
119
+ `${msg.flagged ? "\u2691 flagged " : ""}${msg.hasAttachments ? "\u{1F4CE} attachment " : ""}folder: ${msg.folder}`
120
+ ]
121
+ }]
122
+ })));
123
+ return { exitCode: 0, result: messages };
124
+ } catch (err) {
125
+ handleError(ctx, err, input.flags.json);
126
+ return { exitCode: 1, result: null };
127
+ }
128
+ }
129
+ }
130
+ });
131
+ function parseSince(raw) {
132
+ const match = raw.match(/^(\d+)(h|d|w)$/i);
133
+ if (match) {
134
+ const n = parseInt(match[1] ?? "0", 10);
135
+ const unit = (match[2] ?? "h").toLowerCase();
136
+ const ms = unit === "h" ? n * 36e5 : unit === "d" ? n * 864e5 : n * 7 * 864e5;
137
+ return new Date(Date.now() - ms);
138
+ }
139
+ return new Date(raw);
140
+ }
141
+
142
+ export { mail_list_default as default };
143
+ //# sourceMappingURL=mail-list.js.map
144
+ //# sourceMappingURL=mail-list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/error.ts","../../src/utils/slim.ts","../../src/commands/mail-list.ts"],"names":[],"mappings":"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","file":"mail-list.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, listMessages } from '@kb-labs/inbox-core';\nimport { handleError } from '../utils/error.js';\nimport { formatDate, formatFrom } from '../utils/slim.js';\n\ntype ListFlags = {\n folder?: string;\n unread?: boolean;\n since?: string;\n limit?: number;\n account?: string;\n json?: boolean;\n full?: boolean;\n};\n\nexport default defineCommand({\n id: 'inbox:mail.list',\n description: 'List emails in a folder',\n\n handler: {\n async execute(ctx: PluginContextV3, input: CLIInput<ListFlags>) {\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 listMessages(account, {\n folder: input.flags.folder ?? 'INBOX',\n unreadOnly: input.flags.unread,\n since,\n limit: input.flags.limit ?? 50,\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)}`,\n `${msg.flagged ? '⚑ flagged ' : ''}${msg.hasAttachments ? '📎 attachment ' : ''}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 // Support: 24h, 7d, 2w or ISO 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,20 @@
1
+ import * as _kb_labs_shared_command_kit from '@kb-labs/shared-command-kit';
2
+ import { CLIInput } from '@kb-labs/sdk';
3
+ import { MarkAction } from '@kb-labs/inbox-core';
4
+
5
+ type MarkFlags = {
6
+ read?: boolean;
7
+ unread?: boolean;
8
+ spam?: boolean;
9
+ flagged?: boolean;
10
+ unflagged?: boolean;
11
+ folder?: string;
12
+ account?: string;
13
+ json?: boolean;
14
+ };
15
+ declare const _default: _kb_labs_shared_command_kit.CommandHandlerV3<unknown, CLIInput<MarkFlags>, {
16
+ uid: number;
17
+ action: MarkAction;
18
+ } | null>;
19
+
20
+ export { _default as default };
@@ -0,0 +1,108 @@
1
+ import { defineCommand, validationError } from '@kb-labs/sdk';
2
+ import { resolveAccount, markMessage, InboxError } from '@kb-labs/inbox-core';
3
+
4
+ // src/commands/mail-mark.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-mark.ts
69
+ var mail_mark_default = defineCommand({
70
+ id: "inbox:mail.mark",
71
+ description: "Mark an email as read, unread, spam, or flagged",
72
+ handler: {
73
+ async execute(ctx, input) {
74
+ const [rawUid] = input.argv;
75
+ if (!rawUid) {
76
+ validationError(ctx, "uid is required", "Usage: kb inbox mark <uid> --read|--unread|--spam|--flagged|--unflagged", input.flags.json);
77
+ return { exitCode: 1, result: null };
78
+ }
79
+ const uid = parseInt(rawUid, 10);
80
+ if (isNaN(uid)) {
81
+ validationError(ctx, `Invalid uid: "${rawUid}" \u2014 must be a number`, void 0, input.flags.json);
82
+ return { exitCode: 1, result: null };
83
+ }
84
+ const action = input.flags.read ? "read" : input.flags.unread ? "unread" : input.flags.spam ? "spam" : input.flags.flagged ? "flagged" : input.flags.unflagged ? "unflagged" : void 0;
85
+ if (!action) {
86
+ validationError(ctx, "One of --read, --unread, --spam, --flagged, --unflagged is required", void 0, input.flags.json);
87
+ return { exitCode: 1, result: null };
88
+ }
89
+ try {
90
+ const account = resolveAccount(input.flags.account);
91
+ await markMessage(account, uid, input.flags.folder ?? "INBOX", action);
92
+ if (input.flags.json) {
93
+ ctx.ui?.json?.({ ok: true, result: { uid, action } });
94
+ return { exitCode: 0, result: { uid, action } };
95
+ }
96
+ ctx.ui?.success?.(`Marked uid=${uid} as ${action}`);
97
+ return { exitCode: 0, result: null };
98
+ } catch (err) {
99
+ handleError(ctx, err, input.flags.json);
100
+ return { exitCode: 1, result: null };
101
+ }
102
+ }
103
+ }
104
+ });
105
+
106
+ export { mail_mark_default as default };
107
+ //# sourceMappingURL=mail-mark.js.map
108
+ //# sourceMappingURL=mail-mark.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/error.ts","../../src/commands/mail-mark.ts"],"names":[],"mappings":"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","file":"mail-mark.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, markMessage, type MarkAction } from '@kb-labs/inbox-core';\nimport { handleError } from '../utils/error.js';\n\ntype MarkFlags = {\n read?: boolean;\n unread?: boolean;\n spam?: boolean;\n flagged?: boolean;\n unflagged?: boolean;\n folder?: string;\n account?: string;\n json?: boolean;\n};\n\nexport default defineCommand({\n id: 'inbox:mail.mark',\n description: 'Mark an email as read, unread, spam, or flagged',\n\n handler: {\n async execute(ctx: PluginContextV3, input: CLIInput<MarkFlags>) {\n const [rawUid] = input.argv;\n if (!rawUid) {\n validationError(ctx, 'uid is required', 'Usage: kb inbox mark <uid> --read|--unread|--spam|--flagged|--unflagged', 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 const action: MarkAction | undefined =\n input.flags.read ? 'read' :\n input.flags.unread ? 'unread' :\n input.flags.spam ? 'spam' :\n input.flags.flagged ? 'flagged' :\n input.flags.unflagged ? 'unflagged' :\n undefined;\n\n if (!action) {\n validationError(ctx, 'One of --read, --unread, --spam, --flagged, --unflagged is required', undefined, input.flags.json);\n return { exitCode: 1, result: null };\n }\n\n try {\n const account = resolveAccount(input.flags.account);\n await markMessage(account, uid, input.flags.folder ?? 'INBOX', action);\n\n if (input.flags.json) {\n ctx.ui?.json?.({ ok: true, result: { uid, action } });\n return { exitCode: 0, result: { uid, action } };\n }\n\n ctx.ui?.success?.(`Marked uid=${uid} as ${action}`);\n return { exitCode: 0, result: null };\n } catch (err) {\n handleError(ctx, err, input.flags.json);\n return { exitCode: 1, result: null };\n }\n },\n },\n});\n"]}
@@ -0,0 +1,15 @@
1
+ import * as _kb_labs_shared_command_kit from '@kb-labs/shared-command-kit';
2
+ import { CLIInput } from '@kb-labs/sdk';
3
+
4
+ type MoveFlags = {
5
+ folder?: string;
6
+ from?: string;
7
+ account?: string;
8
+ json?: boolean;
9
+ };
10
+ declare const _default: _kb_labs_shared_command_kit.CommandHandlerV3<unknown, CLIInput<MoveFlags>, {
11
+ uid: number;
12
+ folder: string;
13
+ } | null>;
14
+
15
+ export { _default as default };
@@ -0,0 +1,107 @@
1
+ import { defineCommand, validationError } from '@kb-labs/sdk';
2
+ import { resolveAccount, moveMessage, InboxError } from '@kb-labs/inbox-core';
3
+
4
+ // src/commands/mail-move.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-move.ts
69
+ var mail_move_default = defineCommand({
70
+ id: "inbox:mail.move",
71
+ description: "Move an email to a folder",
72
+ handler: {
73
+ async execute(ctx, input) {
74
+ const [rawUid] = input.argv;
75
+ if (!rawUid) {
76
+ validationError(ctx, "uid is required", "Usage: kb inbox move <uid> --folder Work", input.flags.json);
77
+ return { exitCode: 1, result: null };
78
+ }
79
+ if (!input.flags.folder) {
80
+ validationError(ctx, "--folder is required", void 0, input.flags.json);
81
+ return { exitCode: 1, result: null };
82
+ }
83
+ const uid = parseInt(rawUid, 10);
84
+ if (isNaN(uid)) {
85
+ validationError(ctx, `Invalid uid: "${rawUid}" \u2014 must be a number`, void 0, input.flags.json);
86
+ return { exitCode: 1, result: null };
87
+ }
88
+ try {
89
+ const account = resolveAccount(input.flags.account);
90
+ await moveMessage(account, uid, input.flags.from ?? "INBOX", input.flags.folder);
91
+ if (input.flags.json) {
92
+ ctx.ui?.json?.({ ok: true, result: { uid, folder: input.flags.folder } });
93
+ return { exitCode: 0, result: { uid, folder: input.flags.folder } };
94
+ }
95
+ ctx.ui?.success?.(`Moved uid=${uid} \u2192 ${input.flags.folder}`);
96
+ return { exitCode: 0, result: null };
97
+ } catch (err) {
98
+ handleError(ctx, err, input.flags.json);
99
+ return { exitCode: 1, result: null };
100
+ }
101
+ }
102
+ }
103
+ });
104
+
105
+ export { mail_move_default as default };
106
+ //# sourceMappingURL=mail-move.js.map
107
+ //# sourceMappingURL=mail-move.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/error.ts","../../src/commands/mail-move.ts"],"names":[],"mappings":"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","file":"mail-move.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, moveMessage } from '@kb-labs/inbox-core';\nimport { handleError } from '../utils/error.js';\n\ntype MoveFlags = {\n folder?: string;\n from?: string;\n account?: string;\n json?: boolean;\n};\n\nexport default defineCommand({\n id: 'inbox:mail.move',\n description: 'Move an email to a folder',\n\n handler: {\n async execute(ctx: PluginContextV3, input: CLIInput<MoveFlags>) {\n const [rawUid] = input.argv;\n if (!rawUid) {\n validationError(ctx, 'uid is required', 'Usage: kb inbox move <uid> --folder Work', input.flags.json);\n return { exitCode: 1, result: null };\n }\n if (!input.flags.folder) {\n validationError(ctx, '--folder is required', undefined, 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 await moveMessage(account, uid, input.flags.from ?? 'INBOX', input.flags.folder);\n\n if (input.flags.json) {\n ctx.ui?.json?.({ ok: true, result: { uid, folder: input.flags.folder } });\n return { exitCode: 0, result: { uid, folder: input.flags.folder } };\n }\n\n ctx.ui?.success?.(`Moved uid=${uid} → ${input.flags.folder}`);\n return { exitCode: 0, result: null };\n } catch (err) {\n handleError(ctx, err, input.flags.json);\n return { exitCode: 1, result: null };\n }\n },\n },\n});\n"]}
@@ -0,0 +1,14 @@
1
+ import * as _kb_labs_shared_command_kit from '@kb-labs/shared-command-kit';
2
+ import { CLIInput } from '@kb-labs/sdk';
3
+
4
+ type ReplyFlags = {
5
+ body?: string;
6
+ folder?: string;
7
+ account?: string;
8
+ json?: boolean;
9
+ };
10
+ declare const _default: _kb_labs_shared_command_kit.CommandHandlerV3<unknown, CLIInput<ReplyFlags>, {
11
+ messageId: string;
12
+ } | null>;
13
+
14
+ export { _default as default };
@@ -0,0 +1,114 @@
1
+ import { defineCommand, validationError } from '@kb-labs/sdk';
2
+ import { resolveAccount, getMessage, replyMessage, InboxError } from '@kb-labs/inbox-core';
3
+
4
+ // src/commands/mail-reply.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-reply.ts
69
+ var mail_reply_default = defineCommand({
70
+ id: "inbox:mail.reply",
71
+ description: "Reply to an email by uid (preserves thread)",
72
+ handler: {
73
+ async execute(ctx, input) {
74
+ const [rawUid] = input.argv;
75
+ if (!rawUid) {
76
+ validationError(ctx, "uid is required", 'Usage: kb inbox reply <uid> --body "..."', input.flags.json);
77
+ return { exitCode: 1, result: null };
78
+ }
79
+ if (!input.flags.body) {
80
+ validationError(ctx, "--body is required", void 0, input.flags.json);
81
+ return { exitCode: 1, result: null };
82
+ }
83
+ const uid = parseInt(rawUid, 10);
84
+ if (isNaN(uid)) {
85
+ validationError(ctx, `Invalid uid: "${rawUid}" \u2014 must be a number`, void 0, input.flags.json);
86
+ return { exitCode: 1, result: null };
87
+ }
88
+ try {
89
+ const account = resolveAccount(input.flags.account);
90
+ const folder = input.flags.folder ?? "INBOX";
91
+ const original = await getMessage(account, uid, { folder });
92
+ const result = await replyMessage(account, {
93
+ body: input.flags.body,
94
+ originalMessageId: original.messageId,
95
+ originalReferences: original.references,
96
+ originalSubject: original.subject
97
+ }, input.flags.body);
98
+ if (input.flags.json) {
99
+ ctx.ui?.json?.({ ok: true, result });
100
+ return { exitCode: 0, result };
101
+ }
102
+ ctx.ui?.success?.(`Reply sent id: ${result.messageId}`);
103
+ return { exitCode: 0, result };
104
+ } catch (err) {
105
+ handleError(ctx, err, input.flags.json);
106
+ return { exitCode: 1, result: null };
107
+ }
108
+ }
109
+ }
110
+ });
111
+
112
+ export { mail_reply_default as default };
113
+ //# sourceMappingURL=mail-reply.js.map
114
+ //# sourceMappingURL=mail-reply.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/error.ts","../../src/commands/mail-reply.ts"],"names":[],"mappings":"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","file":"mail-reply.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, getMessage, replyMessage } from '@kb-labs/inbox-core';\nimport { handleError } from '../utils/error.js';\n\ntype ReplyFlags = {\n body?: string;\n folder?: string;\n account?: string;\n json?: boolean;\n};\n\nexport default defineCommand({\n id: 'inbox:mail.reply',\n description: 'Reply to an email by uid (preserves thread)',\n\n handler: {\n async execute(ctx: PluginContextV3, input: CLIInput<ReplyFlags>) {\n const [rawUid] = input.argv;\n if (!rawUid) {\n validationError(ctx, 'uid is required', 'Usage: kb inbox reply <uid> --body \"...\"', 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 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 folder = input.flags.folder ?? 'INBOX';\n\n const original = await getMessage(account, uid, { folder });\n const result = await replyMessage(account, {\n body: input.flags.body,\n originalMessageId: original.messageId,\n originalReferences: original.references,\n originalSubject: original.subject,\n }, input.flags.body);\n\n if (input.flags.json) {\n ctx.ui?.json?.({ ok: true, result });\n return { exitCode: 0, result };\n }\n\n ctx.ui?.success?.(`Reply 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,19 @@
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 SearchFlags = {
6
+ from?: string;
7
+ subject?: string;
8
+ body?: string;
9
+ text?: string;
10
+ folder?: string;
11
+ since?: string;
12
+ limit?: number;
13
+ account?: string;
14
+ json?: boolean;
15
+ full?: boolean;
16
+ };
17
+ declare const _default: _kb_labs_shared_command_kit.CommandHandlerV3<unknown, CLIInput<SearchFlags>, _kb_labs_inbox_contracts.EmailSlim[] | null>;
18
+
19
+ export { _default as default };