shellmail 1.0.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/dist/api.d.ts ADDED
@@ -0,0 +1,90 @@
1
+ /**
2
+ * ShellMail API Client
3
+ */
4
+ export interface CreateAddressResponse {
5
+ address: string;
6
+ token: string;
7
+ note: string;
8
+ }
9
+ export interface EmailSummary {
10
+ id: string;
11
+ from_addr: string;
12
+ from_name: string | null;
13
+ subject: string;
14
+ received_at: string;
15
+ is_read: boolean;
16
+ otp_code?: string | null;
17
+ otp_link?: string | null;
18
+ }
19
+ export interface EmailDetail extends EmailSummary {
20
+ body_text: string | null;
21
+ body_html: string | null;
22
+ is_archived: boolean;
23
+ }
24
+ export interface InboxResponse {
25
+ address: string;
26
+ unread_count: number;
27
+ emails: EmailSummary[];
28
+ }
29
+ export interface OtpResponse {
30
+ found: boolean;
31
+ email_id?: string;
32
+ from?: string;
33
+ subject?: string;
34
+ code?: string | null;
35
+ link?: string | null;
36
+ received_at?: string;
37
+ message?: string;
38
+ }
39
+ export interface WebhookResponse {
40
+ configured: boolean;
41
+ url: string | null;
42
+ has_secret: boolean;
43
+ }
44
+ export interface WebhookSetResponse {
45
+ ok: boolean;
46
+ url: string;
47
+ secret: string;
48
+ note: string;
49
+ }
50
+ export declare class ShellMailAPI {
51
+ private token;
52
+ constructor(token?: string);
53
+ private request;
54
+ createAddress(local: string, recoveryEmail: string): Promise<CreateAddressResponse>;
55
+ inbox(unreadOnly?: boolean, limit?: number): Promise<InboxResponse>;
56
+ read(emailId: string): Promise<EmailDetail>;
57
+ markRead(emailId: string): Promise<{
58
+ ok: boolean;
59
+ }>;
60
+ archive(emailId: string): Promise<{
61
+ ok: boolean;
62
+ }>;
63
+ delete(emailId: string): Promise<{
64
+ ok: boolean;
65
+ }>;
66
+ otp(options?: {
67
+ timeout?: number;
68
+ from?: string;
69
+ since?: string;
70
+ }): Promise<OtpResponse>;
71
+ search(options: {
72
+ q?: string;
73
+ from?: string;
74
+ hasOtp?: boolean;
75
+ limit?: number;
76
+ }): Promise<{
77
+ count: number;
78
+ emails: EmailSummary[];
79
+ }>;
80
+ getWebhook(): Promise<WebhookResponse>;
81
+ setWebhook(url: string, secret?: string): Promise<WebhookSetResponse>;
82
+ deleteWebhook(): Promise<{
83
+ ok: boolean;
84
+ }>;
85
+ health(): Promise<{
86
+ service: string;
87
+ status: string;
88
+ domain: string;
89
+ }>;
90
+ }
package/dist/api.js ADDED
@@ -0,0 +1,90 @@
1
+ /**
2
+ * ShellMail API Client
3
+ */
4
+ const API_BASE = process.env.SHELLMAIL_API_URL || "https://shellmail.ai";
5
+ export class ShellMailAPI {
6
+ token;
7
+ constructor(token) {
8
+ this.token = token || process.env.SHELLMAIL_TOKEN || null;
9
+ }
10
+ async request(method, path, body, requireAuth = true) {
11
+ const headers = {
12
+ "Content-Type": "application/json",
13
+ };
14
+ if (requireAuth) {
15
+ if (!this.token) {
16
+ throw new Error("No token configured. Run 'shellmail setup' first.");
17
+ }
18
+ headers["Authorization"] = `Bearer ${this.token}`;
19
+ }
20
+ const response = await fetch(`${API_BASE}${path}`, {
21
+ method,
22
+ headers,
23
+ body: body ? JSON.stringify(body) : undefined,
24
+ });
25
+ const data = await response.json();
26
+ if (!response.ok) {
27
+ throw new Error(data.error || `Request failed: ${response.status}`);
28
+ }
29
+ return data;
30
+ }
31
+ async createAddress(local, recoveryEmail) {
32
+ return this.request("POST", "/api/addresses", { local, recovery_email: recoveryEmail }, false);
33
+ }
34
+ async inbox(unreadOnly = false, limit = 20) {
35
+ const params = new URLSearchParams();
36
+ if (unreadOnly)
37
+ params.set("unread", "true");
38
+ params.set("limit", limit.toString());
39
+ return this.request("GET", `/api/mail?${params}`);
40
+ }
41
+ async read(emailId) {
42
+ return this.request("GET", `/api/mail/${emailId}`);
43
+ }
44
+ async markRead(emailId) {
45
+ return this.request("PATCH", `/api/mail/${emailId}`, { is_read: true });
46
+ }
47
+ async archive(emailId) {
48
+ return this.request("PATCH", `/api/mail/${emailId}`, { is_archived: true });
49
+ }
50
+ async delete(emailId) {
51
+ return this.request("DELETE", `/api/mail/${emailId}`);
52
+ }
53
+ async otp(options) {
54
+ const params = new URLSearchParams();
55
+ if (options?.timeout)
56
+ params.set("timeout", options.timeout.toString());
57
+ if (options?.from)
58
+ params.set("from", options.from);
59
+ if (options?.since)
60
+ params.set("since", options.since);
61
+ return this.request("GET", `/api/mail/otp?${params}`);
62
+ }
63
+ async search(options) {
64
+ const params = new URLSearchParams();
65
+ if (options.q)
66
+ params.set("q", options.q);
67
+ if (options.from)
68
+ params.set("from", options.from);
69
+ if (options.hasOtp)
70
+ params.set("has_otp", "true");
71
+ if (options.limit)
72
+ params.set("limit", options.limit.toString());
73
+ return this.request("GET", `/api/mail/search?${params}`);
74
+ }
75
+ async getWebhook() {
76
+ return this.request("GET", "/api/webhook");
77
+ }
78
+ async setWebhook(url, secret) {
79
+ return this.request("PUT", "/api/webhook", {
80
+ url,
81
+ secret,
82
+ });
83
+ }
84
+ async deleteWebhook() {
85
+ return this.request("DELETE", "/api/webhook");
86
+ }
87
+ async health() {
88
+ return this.request("GET", "/health", undefined, false);
89
+ }
90
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Configuration management for ShellMail CLI
3
+ */
4
+ export interface ShellMailConfig {
5
+ token?: string;
6
+ address?: string;
7
+ apiUrl?: string;
8
+ }
9
+ export declare function loadConfig(): ShellMailConfig;
10
+ export declare function saveConfig(config: ShellMailConfig): void;
11
+ export declare function clearConfig(): void;
12
+ export declare function getToken(): string | undefined;
package/dist/config.js ADDED
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Configuration management for ShellMail CLI
3
+ */
4
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
5
+ import { homedir } from "os";
6
+ import { join } from "path";
7
+ const CONFIG_DIR = join(homedir(), ".shellmail");
8
+ const CONFIG_FILE = join(CONFIG_DIR, "config.json");
9
+ export function loadConfig() {
10
+ // Environment variables take precedence
11
+ if (process.env.SHELLMAIL_TOKEN) {
12
+ return {
13
+ token: process.env.SHELLMAIL_TOKEN,
14
+ address: process.env.SHELLMAIL_ADDRESS,
15
+ apiUrl: process.env.SHELLMAIL_API_URL,
16
+ };
17
+ }
18
+ // Try to load from config file
19
+ if (existsSync(CONFIG_FILE)) {
20
+ try {
21
+ const data = readFileSync(CONFIG_FILE, "utf-8");
22
+ return JSON.parse(data);
23
+ }
24
+ catch {
25
+ return {};
26
+ }
27
+ }
28
+ return {};
29
+ }
30
+ export function saveConfig(config) {
31
+ if (!existsSync(CONFIG_DIR)) {
32
+ mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
33
+ }
34
+ writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), {
35
+ mode: 0o600, // Only owner can read/write
36
+ });
37
+ }
38
+ export function clearConfig() {
39
+ if (existsSync(CONFIG_FILE)) {
40
+ writeFileSync(CONFIG_FILE, "{}", { mode: 0o600 });
41
+ }
42
+ }
43
+ export function getToken() {
44
+ return process.env.SHELLMAIL_TOKEN || loadConfig().token;
45
+ }
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ShellMail CLI
4
+ * Email for AI agents — in 30 seconds
5
+ */
6
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,433 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ShellMail CLI
4
+ * Email for AI agents — in 30 seconds
5
+ */
6
+ import { Command } from "commander";
7
+ import chalk from "chalk";
8
+ import ora from "ora";
9
+ import inquirer from "inquirer";
10
+ import { ShellMailAPI } from "./api.js";
11
+ import { loadConfig, saveConfig, clearConfig, getToken } from "./config.js";
12
+ const program = new Command();
13
+ program
14
+ .name("shellmail")
15
+ .description("Email for AI agents — create addresses, check mail, extract OTPs")
16
+ .version("1.0.0");
17
+ // ── Setup Command ────────────────────────────────────────
18
+ program
19
+ .command("setup")
20
+ .description("Create a new ShellMail address interactively")
21
+ .option("-l, --local <name>", "Local part of email address")
22
+ .option("-r, --recovery <email>", "Recovery email address")
23
+ .action(async (options) => {
24
+ console.log(chalk.bold("\n📧 ShellMail Setup\n"));
25
+ let local = options.local;
26
+ let recoveryEmail = options.recovery;
27
+ if (!local) {
28
+ const answers = await inquirer.prompt([
29
+ {
30
+ type: "input",
31
+ name: "local",
32
+ message: "Choose your email address:",
33
+ suffix: chalk.gray("@shellmail.ai"),
34
+ validate: (input) => {
35
+ if (!input || input.length < 2)
36
+ return "Must be at least 2 characters";
37
+ if (!/^[a-z0-9][a-z0-9._-]*[a-z0-9]$/i.test(input) && input.length > 2) {
38
+ return "Only letters, numbers, dots, hyphens, underscores allowed";
39
+ }
40
+ return true;
41
+ },
42
+ },
43
+ ]);
44
+ local = answers.local;
45
+ }
46
+ if (!recoveryEmail) {
47
+ const answers = await inquirer.prompt([
48
+ {
49
+ type: "input",
50
+ name: "recovery",
51
+ message: "Recovery email (for token recovery):",
52
+ validate: (input) => {
53
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input)) {
54
+ return "Enter a valid email address";
55
+ }
56
+ return true;
57
+ },
58
+ },
59
+ ]);
60
+ recoveryEmail = answers.recovery;
61
+ }
62
+ const spinner = ora("Creating address...").start();
63
+ try {
64
+ const api = new ShellMailAPI();
65
+ const result = await api.createAddress(local, recoveryEmail);
66
+ spinner.succeed(chalk.green("Address created!"));
67
+ console.log("\n" + chalk.bold("Your ShellMail address:"));
68
+ console.log(chalk.cyan(` ${result.address}\n`));
69
+ console.log(chalk.bold("Your API token:"));
70
+ console.log(chalk.yellow(` ${result.token}\n`));
71
+ console.log(chalk.gray("⚠️ Save this token! It won't be shown again.\n"));
72
+ // Save to config
73
+ const { save } = await inquirer.prompt([
74
+ {
75
+ type: "confirm",
76
+ name: "save",
77
+ message: "Save token to ~/.shellmail/config.json?",
78
+ default: true,
79
+ },
80
+ ]);
81
+ if (save) {
82
+ saveConfig({
83
+ token: result.token,
84
+ address: result.address,
85
+ });
86
+ console.log(chalk.green("\n✓ Config saved! You can now use other shellmail commands.\n"));
87
+ }
88
+ else {
89
+ console.log(chalk.gray("\nSet SHELLMAIL_TOKEN env var to use the CLI:\n"));
90
+ console.log(chalk.cyan(` export SHELLMAIL_TOKEN="${result.token}"\n`));
91
+ }
92
+ }
93
+ catch (err) {
94
+ spinner.fail(chalk.red("Failed to create address"));
95
+ console.error(chalk.red(` ${err.message}\n`));
96
+ process.exit(1);
97
+ }
98
+ });
99
+ // ── Inbox Command ────────────────────────────────────────
100
+ program
101
+ .command("inbox")
102
+ .description("List emails in your inbox")
103
+ .option("-u, --unread", "Show only unread emails")
104
+ .option("-n, --limit <number>", "Number of emails to show", "10")
105
+ .action(async (options) => {
106
+ const token = getToken();
107
+ if (!token) {
108
+ console.error(chalk.red("No token configured. Run 'shellmail setup' first."));
109
+ process.exit(1);
110
+ }
111
+ const spinner = ora("Fetching inbox...").start();
112
+ try {
113
+ const api = new ShellMailAPI(token);
114
+ const result = await api.inbox(options.unread, parseInt(options.limit));
115
+ spinner.stop();
116
+ console.log(chalk.bold(`\n📬 ${result.address}`));
117
+ console.log(chalk.gray(` ${result.unread_count} unread\n`));
118
+ if (result.emails.length === 0) {
119
+ console.log(chalk.gray(" No emails.\n"));
120
+ return;
121
+ }
122
+ for (const email of result.emails) {
123
+ const unread = !email.is_read ? chalk.blue("●") : " ";
124
+ const from = email.from_name || email.from_addr.split("@")[0];
125
+ const date = new Date(email.received_at).toLocaleString();
126
+ const otp = email.otp_code ? chalk.yellow(` [OTP: ${email.otp_code}]`) : "";
127
+ console.log(`${unread} ${chalk.bold(from.slice(0, 20).padEnd(20))} ${email.subject?.slice(0, 40) || "(no subject)"}${otp}`);
128
+ console.log(chalk.gray(` ${email.id} ${date}\n`));
129
+ }
130
+ }
131
+ catch (err) {
132
+ spinner.fail(chalk.red("Failed to fetch inbox"));
133
+ console.error(chalk.red(` ${err.message}\n`));
134
+ process.exit(1);
135
+ }
136
+ });
137
+ // ── Read Command ─────────────────────────────────────────
138
+ program
139
+ .command("read <id>")
140
+ .description("Read a specific email")
141
+ .option("-m, --mark-read", "Mark as read after viewing", true)
142
+ .action(async (id, options) => {
143
+ const token = getToken();
144
+ if (!token) {
145
+ console.error(chalk.red("No token configured. Run 'shellmail setup' first."));
146
+ process.exit(1);
147
+ }
148
+ const spinner = ora("Fetching email...").start();
149
+ try {
150
+ const api = new ShellMailAPI(token);
151
+ const email = await api.read(id);
152
+ if (options.markRead && !email.is_read) {
153
+ await api.markRead(id);
154
+ }
155
+ spinner.stop();
156
+ console.log("\n" + chalk.bold("From: ") + (email.from_name ? `${email.from_name} <${email.from_addr}>` : email.from_addr));
157
+ console.log(chalk.bold("Subject: ") + (email.subject || "(no subject)"));
158
+ console.log(chalk.bold("Date: ") + new Date(email.received_at).toLocaleString());
159
+ console.log(chalk.gray("─".repeat(60)) + "\n");
160
+ if (email.body_text) {
161
+ // Clean up quoted-printable encoding
162
+ const body = email.body_text
163
+ .replace(/=\r?\n/g, "")
164
+ .replace(/=([0-9A-F]{2})/gi, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
165
+ console.log(body);
166
+ }
167
+ else if (email.body_html) {
168
+ // Strip HTML tags for terminal display
169
+ const text = email.body_html
170
+ .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "")
171
+ .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "")
172
+ .replace(/<[^>]+>/g, " ")
173
+ .replace(/\s+/g, " ")
174
+ .trim();
175
+ console.log(text);
176
+ }
177
+ else {
178
+ console.log(chalk.gray("(no content)"));
179
+ }
180
+ console.log("\n");
181
+ }
182
+ catch (err) {
183
+ spinner.fail(chalk.red("Failed to read email"));
184
+ console.error(chalk.red(` ${err.message}\n`));
185
+ process.exit(1);
186
+ }
187
+ });
188
+ // ── OTP Command ──────────────────────────────────────────
189
+ program
190
+ .command("otp")
191
+ .description("Get the latest OTP/verification code")
192
+ .option("-w, --wait <seconds>", "Wait for OTP to arrive (max 30s)")
193
+ .option("-f, --from <domain>", "Filter by sender domain")
194
+ .action(async (options) => {
195
+ const token = getToken();
196
+ if (!token) {
197
+ console.error(chalk.red("No token configured. Run 'shellmail setup' first."));
198
+ process.exit(1);
199
+ }
200
+ const timeout = options.wait ? Math.min(parseInt(options.wait) * 1000, 30000) : 0;
201
+ const spinner = timeout
202
+ ? ora(`Waiting for OTP (${options.wait}s timeout)...`).start()
203
+ : ora("Checking for OTP...").start();
204
+ try {
205
+ const api = new ShellMailAPI(token);
206
+ const result = await api.otp({
207
+ timeout,
208
+ from: options.from,
209
+ });
210
+ if (result.found) {
211
+ spinner.succeed(chalk.green("OTP found!"));
212
+ console.log("\n" + chalk.bold.yellow(` ${result.code || result.link}\n`));
213
+ console.log(chalk.gray(` From: ${result.from}`));
214
+ console.log(chalk.gray(` Subject: ${result.subject}`));
215
+ console.log(chalk.gray(` Received: ${new Date(result.received_at).toLocaleString()}\n`));
216
+ // Output just the code for piping
217
+ if (process.stdout.isTTY === false) {
218
+ process.stdout.write(result.code || result.link || "");
219
+ }
220
+ }
221
+ else {
222
+ spinner.warn(chalk.yellow(result.message || "No OTP found"));
223
+ console.log(chalk.gray("\n Tip: Send a verification email to your address, then try again.\n"));
224
+ process.exit(1);
225
+ }
226
+ }
227
+ catch (err) {
228
+ spinner.fail(chalk.red("Failed to check OTP"));
229
+ console.error(chalk.red(` ${err.message}\n`));
230
+ process.exit(1);
231
+ }
232
+ });
233
+ // ── Search Command ───────────────────────────────────────
234
+ program
235
+ .command("search")
236
+ .description("Search emails")
237
+ .option("-q, --query <text>", "Search text in subject/body/sender")
238
+ .option("-f, --from <domain>", "Filter by sender")
239
+ .option("--otp", "Only show emails with OTP codes")
240
+ .option("-n, --limit <number>", "Number of results", "10")
241
+ .action(async (options) => {
242
+ const token = getToken();
243
+ if (!token) {
244
+ console.error(chalk.red("No token configured. Run 'shellmail setup' first."));
245
+ process.exit(1);
246
+ }
247
+ if (!options.query && !options.from && !options.otp) {
248
+ console.error(chalk.red("Provide at least one search option: --query, --from, or --otp"));
249
+ process.exit(1);
250
+ }
251
+ const spinner = ora("Searching...").start();
252
+ try {
253
+ const api = new ShellMailAPI(token);
254
+ const result = await api.search({
255
+ q: options.query,
256
+ from: options.from,
257
+ hasOtp: options.otp,
258
+ limit: parseInt(options.limit),
259
+ });
260
+ spinner.stop();
261
+ console.log(chalk.bold(`\n🔍 ${result.count} result(s)\n`));
262
+ for (const email of result.emails) {
263
+ const from = email.from_name || email.from_addr.split("@")[0];
264
+ const otp = email.otp_code ? chalk.yellow(` [OTP: ${email.otp_code}]`) : "";
265
+ console.log(`${chalk.bold(from.slice(0, 20).padEnd(20))} ${email.subject?.slice(0, 40) || "(no subject)"}${otp}`);
266
+ console.log(chalk.gray(` ${email.id}\n`));
267
+ }
268
+ }
269
+ catch (err) {
270
+ spinner.fail(chalk.red("Search failed"));
271
+ console.error(chalk.red(` ${err.message}\n`));
272
+ process.exit(1);
273
+ }
274
+ });
275
+ // ── Webhook Command ──────────────────────────────────────
276
+ program
277
+ .command("webhook")
278
+ .description("Configure webhook notifications")
279
+ .option("-s, --set <url>", "Set webhook URL")
280
+ .option("-d, --delete", "Remove webhook configuration")
281
+ .action(async (options) => {
282
+ const token = getToken();
283
+ if (!token) {
284
+ console.error(chalk.red("No token configured. Run 'shellmail setup' first."));
285
+ process.exit(1);
286
+ }
287
+ const api = new ShellMailAPI(token);
288
+ if (options.delete) {
289
+ const spinner = ora("Removing webhook...").start();
290
+ try {
291
+ await api.deleteWebhook();
292
+ spinner.succeed(chalk.green("Webhook removed"));
293
+ }
294
+ catch (err) {
295
+ spinner.fail(chalk.red("Failed to remove webhook"));
296
+ console.error(chalk.red(` ${err.message}\n`));
297
+ process.exit(1);
298
+ }
299
+ return;
300
+ }
301
+ if (options.set) {
302
+ const spinner = ora("Configuring webhook...").start();
303
+ try {
304
+ const result = await api.setWebhook(options.set);
305
+ spinner.succeed(chalk.green("Webhook configured!"));
306
+ console.log("\n" + chalk.bold("URL: ") + result.url);
307
+ console.log(chalk.bold("Secret: ") + chalk.yellow(result.secret));
308
+ console.log(chalk.gray("\n⚠️ Save the secret! Use it to verify webhook signatures.\n"));
309
+ }
310
+ catch (err) {
311
+ spinner.fail(chalk.red("Failed to configure webhook"));
312
+ console.error(chalk.red(` ${err.message}\n`));
313
+ process.exit(1);
314
+ }
315
+ return;
316
+ }
317
+ // Show current config
318
+ const spinner = ora("Fetching webhook config...").start();
319
+ try {
320
+ const result = await api.getWebhook();
321
+ spinner.stop();
322
+ if (result.configured) {
323
+ console.log(chalk.bold("\n🔔 Webhook configured"));
324
+ console.log(chalk.bold("URL: ") + result.url);
325
+ console.log(chalk.bold("Secret: ") + (result.has_secret ? chalk.green("configured") : chalk.yellow("not set")));
326
+ }
327
+ else {
328
+ console.log(chalk.gray("\nNo webhook configured."));
329
+ console.log(chalk.gray("Use 'shellmail webhook --set <url>' to configure.\n"));
330
+ }
331
+ }
332
+ catch (err) {
333
+ spinner.fail(chalk.red("Failed to fetch webhook config"));
334
+ console.error(chalk.red(` ${err.message}\n`));
335
+ process.exit(1);
336
+ }
337
+ });
338
+ // ── Delete Command ───────────────────────────────────────
339
+ program
340
+ .command("delete <id>")
341
+ .description("Delete an email")
342
+ .action(async (id) => {
343
+ const token = getToken();
344
+ if (!token) {
345
+ console.error(chalk.red("No token configured. Run 'shellmail setup' first."));
346
+ process.exit(1);
347
+ }
348
+ const spinner = ora("Deleting email...").start();
349
+ try {
350
+ const api = new ShellMailAPI(token);
351
+ await api.delete(id);
352
+ spinner.succeed(chalk.green("Email deleted"));
353
+ }
354
+ catch (err) {
355
+ spinner.fail(chalk.red("Failed to delete email"));
356
+ console.error(chalk.red(` ${err.message}\n`));
357
+ process.exit(1);
358
+ }
359
+ });
360
+ // ── Archive Command ──────────────────────────────────────
361
+ program
362
+ .command("archive <id>")
363
+ .description("Archive an email")
364
+ .action(async (id) => {
365
+ const token = getToken();
366
+ if (!token) {
367
+ console.error(chalk.red("No token configured. Run 'shellmail setup' first."));
368
+ process.exit(1);
369
+ }
370
+ const spinner = ora("Archiving email...").start();
371
+ try {
372
+ const api = new ShellMailAPI(token);
373
+ await api.archive(id);
374
+ spinner.succeed(chalk.green("Email archived"));
375
+ }
376
+ catch (err) {
377
+ spinner.fail(chalk.red("Failed to archive email"));
378
+ console.error(chalk.red(` ${err.message}\n`));
379
+ process.exit(1);
380
+ }
381
+ });
382
+ // ── Logout Command ───────────────────────────────────────
383
+ program
384
+ .command("logout")
385
+ .description("Clear saved configuration")
386
+ .action(async () => {
387
+ const { confirm } = await inquirer.prompt([
388
+ {
389
+ type: "confirm",
390
+ name: "confirm",
391
+ message: "This will remove your saved token. Continue?",
392
+ default: false,
393
+ },
394
+ ]);
395
+ if (confirm) {
396
+ clearConfig();
397
+ console.log(chalk.green("\n✓ Configuration cleared.\n"));
398
+ }
399
+ });
400
+ // ── Status Command ───────────────────────────────────────
401
+ program
402
+ .command("status")
403
+ .description("Check ShellMail service status and current config")
404
+ .action(async () => {
405
+ const config = loadConfig();
406
+ const api = new ShellMailAPI();
407
+ console.log(chalk.bold("\n📧 ShellMail Status\n"));
408
+ // Check service health
409
+ const spinner = ora("Checking service...").start();
410
+ try {
411
+ const health = await api.health();
412
+ spinner.succeed(chalk.green(`Service: ${health.status}`));
413
+ }
414
+ catch {
415
+ spinner.fail(chalk.red("Service: unreachable"));
416
+ }
417
+ // Show config
418
+ if (config.token) {
419
+ console.log(chalk.green("✓ Token: configured"));
420
+ if (config.address) {
421
+ console.log(chalk.green(`✓ Address: ${config.address}`));
422
+ }
423
+ }
424
+ else if (process.env.SHELLMAIL_TOKEN) {
425
+ console.log(chalk.green("✓ Token: set via SHELLMAIL_TOKEN"));
426
+ }
427
+ else {
428
+ console.log(chalk.yellow("✗ Token: not configured"));
429
+ console.log(chalk.gray(" Run 'shellmail setup' to create an address"));
430
+ }
431
+ console.log("");
432
+ });
433
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "shellmail",
3
+ "version": "1.0.0",
4
+ "description": "CLI for ShellMail - Email for AI agents",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "shellmail": "./dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsc -w",
13
+ "prepublishOnly": "npm run build"
14
+ },
15
+ "keywords": [
16
+ "email",
17
+ "ai",
18
+ "agents",
19
+ "cli",
20
+ "shellmail"
21
+ ],
22
+ "author": "",
23
+ "license": "MIT",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/aaronbatchelder/shellmail"
27
+ },
28
+ "dependencies": {
29
+ "chalk": "^5.3.0",
30
+ "commander": "^12.1.0",
31
+ "inquirer": "^9.2.15",
32
+ "ora": "^8.0.1"
33
+ },
34
+ "devDependencies": {
35
+ "@types/inquirer": "^9.0.7",
36
+ "@types/node": "^20.11.0",
37
+ "typescript": "^5.3.3"
38
+ },
39
+ "engines": {
40
+ "node": ">=18"
41
+ },
42
+ "files": [
43
+ "dist"
44
+ ]
45
+ }