@mailmodo/cli 0.0.8-beta.pr10.14 → 0.0.8

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.
@@ -34,13 +34,15 @@ export default class Login extends BaseCommand {
34
34
  validate(value) {
35
35
  if (!value?.trim())
36
36
  return 'API key is required';
37
+ if (!value.startsWith('mm_'))
38
+ return 'Invalid API key format. Keys start with mm_';
37
39
  return true;
38
40
  },
39
41
  });
40
42
  }
41
43
  const trimmedKey = apiKey.trim();
42
44
  const client = new ApiClient(trimmedKey);
43
- const response = await client.post(API_ENDPOINTS.AUTH_VALIDATE, {});
45
+ const response = await client.get(API_ENDPOINTS.AUTH_VALIDATE);
44
46
  if (!response.ok) {
45
47
  this.handleApiError(response);
46
48
  }
@@ -1,29 +1,26 @@
1
- /**
2
- * Request context attached to API responses for troubleshooting failed calls.
3
- * Only the resolved URL is stored; it encodes origin, path, and query string.
4
- */
5
- export interface ApiRequestDebugInfo {
6
- /** Node/DNS/network error code when `fetch` throws before an HTTP response. */
7
- causeCode?: string;
8
- /** Fully resolved request URL (origin, path, and query string). */
9
- fullUrl: string;
10
- /** Truncated JSON summary of a non-empty error response body, when available. */
11
- responseSummary?: string;
12
- }
13
1
  export interface ApiResponse<T = Record<string, unknown>> {
14
2
  data: T;
15
- /** Populated for tracing; especially useful when `ok` is false. */
16
- debug?: ApiRequestDebugInfo;
17
3
  error?: string;
18
4
  ok: boolean;
19
5
  status: number;
20
6
  }
7
+ /**
8
+ * HTTP client for the Mailmodo CLI API.
9
+ * Wraps the native fetch API with Bearer token authentication,
10
+ * consistent error handling, and typed responses.
11
+ *
12
+ * All requests include:
13
+ * - Authorization header with the user's API key
14
+ * - Content-Type: application/json
15
+ * - User-Agent: @mailmodo/cli
16
+ *
17
+ * Network errors (ECONNREFUSED, ENOTFOUND) return a friendly message
18
+ * indicating the API may not be available, rather than a raw stack trace.
19
+ */
21
20
  export declare class ApiClient {
22
21
  private apiKey;
23
22
  private baseUrl;
24
23
  constructor(apiKey: string);
25
- private resolveUrl;
26
- private requestDebug;
27
24
  /**
28
25
  * Sends an HTTP request to the Mailmodo API and returns a typed response.
29
26
  *
@@ -12,22 +12,6 @@ import { API_BASE_URL } from './constants.js';
12
12
  * Network errors (ECONNREFUSED, ENOTFOUND) return a friendly message
13
13
  * indicating the API may not be available, rather than a raw stack trace.
14
14
  */
15
- const RESPONSE_BODY_DEBUG_MAX = 800;
16
- function summarizeResponseBody(data) {
17
- if (data === null || data === undefined)
18
- return undefined;
19
- try {
20
- const s = JSON.stringify(data);
21
- if (s === '{}' || s === '[]')
22
- return undefined;
23
- return s.length > RESPONSE_BODY_DEBUG_MAX
24
- ? `${s.slice(0, RESPONSE_BODY_DEBUG_MAX)}…`
25
- : s;
26
- }
27
- catch {
28
- return undefined;
29
- }
30
- }
31
15
  export class ApiClient {
32
16
  apiKey;
33
17
  baseUrl;
@@ -35,18 +19,6 @@ export class ApiClient {
35
19
  this.baseUrl = API_BASE_URL;
36
20
  this.apiKey = apiKey;
37
21
  }
38
- resolveUrl(path, params) {
39
- const url = new URL(`${this.baseUrl}${path}`);
40
- if (params) {
41
- for (const [key, value] of Object.entries(params)) {
42
- url.searchParams.set(key, value);
43
- }
44
- }
45
- return url;
46
- }
47
- requestDebug(url) {
48
- return { fullUrl: url.toString() };
49
- }
50
22
  /**
51
23
  * Sends an HTTP request to the Mailmodo API and returns a typed response.
52
24
  *
@@ -59,8 +31,12 @@ export class ApiClient {
59
31
  * data (parsed JSON body), and optional error (string message on failure).
60
32
  */
61
33
  async request(method, path, body, params) {
62
- const url = this.resolveUrl(path, params);
63
- const debug = this.requestDebug(url);
34
+ const url = new URL(`${this.baseUrl}${path}`);
35
+ if (params) {
36
+ for (const [key, value] of Object.entries(params)) {
37
+ url.searchParams.set(key, value);
38
+ }
39
+ }
64
40
  const headers = {
65
41
  Authorization: `Bearer ${this.apiKey}`,
66
42
  'Content-Type': 'application/json',
@@ -75,13 +51,8 @@ export class ApiClient {
75
51
  const data = await response.json().catch(() => ({}));
76
52
  if (!response.ok) {
77
53
  const errorData = data;
78
- const summary = summarizeResponseBody(data);
79
54
  return {
80
55
  data: data,
81
- debug: {
82
- ...debug,
83
- responseSummary: summary,
84
- },
85
56
  error: errorData?.message ||
86
57
  errorData?.error ||
87
58
  `Request failed with status ${response.status}`,
@@ -94,13 +65,8 @@ export class ApiClient {
94
65
  catch (error) {
95
66
  const err = error;
96
67
  const isConnectionError = err?.cause?.code === 'ECONNREFUSED' || err?.cause?.code === 'ENOTFOUND';
97
- const causeCode = err?.cause?.code;
98
68
  return {
99
69
  data: {},
100
- debug: {
101
- ...debug,
102
- ...(causeCode ? { causeCode } : {}),
103
- },
104
70
  error: isConnectionError
105
71
  ? 'Cannot connect to Mailmodo API. The API service may not be available yet.'
106
72
  : err?.message || 'An unexpected network error occurred.',
@@ -122,8 +88,7 @@ export class ApiClient {
122
88
  return this.request('POST', path, body);
123
89
  }
124
90
  async postFormData(path, formData) {
125
- const url = this.resolveUrl(path);
126
- const debug = this.requestDebug(url);
91
+ const url = new URL(`${this.baseUrl}${path}`);
127
92
  try {
128
93
  const response = await fetch(url.toString(), {
129
94
  body: formData,
@@ -136,13 +101,8 @@ export class ApiClient {
136
101
  const data = await response.json().catch(() => ({}));
137
102
  if (!response.ok) {
138
103
  const errorData = data;
139
- const summary = summarizeResponseBody(data);
140
104
  return {
141
105
  data: data,
142
- debug: {
143
- ...debug,
144
- responseSummary: summary,
145
- },
146
106
  error: errorData?.message ||
147
107
  errorData?.error ||
148
108
  `Upload failed with status ${response.status}`,
@@ -154,13 +114,8 @@ export class ApiClient {
154
114
  }
155
115
  catch (error) {
156
116
  const err = error;
157
- const causeCode = err?.cause?.code;
158
117
  return {
159
118
  data: {},
160
- debug: {
161
- ...debug,
162
- ...(causeCode ? { causeCode } : {}),
163
- },
164
119
  error: err?.message || 'File upload failed.',
165
120
  ok: false,
166
121
  status: 0,
@@ -1,5 +1,5 @@
1
1
  import { Command } from '@oclif/core';
2
- import { ApiClient, type ApiRequestDebugInfo } from './api-client.js';
2
+ import { ApiClient } from './api-client.js';
3
3
  import { type MailmodoConfig } from './config.js';
4
4
  import { type MailmodoYaml } from './yaml-config.js';
5
5
  /**
@@ -34,24 +34,12 @@ export declare abstract class BaseCommand extends Command {
34
34
  * Handles a failed API response by mapping HTTP status codes to
35
35
  * user-friendly error messages and exiting the process.
36
36
  *
37
- * @param {{ status: number; error?: string; debug?: ApiRequestDebugInfo }} response - The API response object with ok=false.
37
+ * @param {{ status: number; error?: string }} response - The API response object with ok=false.
38
38
  * Status 401 prompts re-authentication, 429 indicates rate limiting,
39
39
  * all others display the server's error message or a generic fallback.
40
- * When `debug` is present, extra lines list Full URL, Status, optional Response
41
- * body summary, and optional Error Code (network failures).
42
40
  */
43
41
  protected handleApiError(response: {
44
- debug?: ApiRequestDebugInfo;
45
42
  error?: string;
46
43
  status: number;
47
44
  }): never;
48
- /**
49
- * Builds the terminal error string for a failed API call, appending request
50
- * metadata when {@link ApiRequestDebugInfo} is available.
51
- *
52
- * @param {string} message - Primary error text (HTTP message or generic).
53
- * @param {{ status: number; debug?: ApiRequestDebugInfo }} response - Failed API response.
54
- * @returns {string} Message plus indented Request details for troubleshooting.
55
- */
56
- private formatApiFailure;
57
45
  }
@@ -57,48 +57,17 @@ export class BaseCommand extends Command {
57
57
  * Handles a failed API response by mapping HTTP status codes to
58
58
  * user-friendly error messages and exiting the process.
59
59
  *
60
- * @param {{ status: number; error?: string; debug?: ApiRequestDebugInfo }} response - The API response object with ok=false.
60
+ * @param {{ status: number; error?: string }} response - The API response object with ok=false.
61
61
  * Status 401 prompts re-authentication, 429 indicates rate limiting,
62
62
  * all others display the server's error message or a generic fallback.
63
- * When `debug` is present, extra lines list Full URL, Status, optional Response
64
- * body summary, and optional Error Code (network failures).
65
63
  */
66
64
  handleApiError(response) {
67
65
  if (response.status === 401) {
68
- this.error(this.formatApiFailure(`Invalid API key. Run ${chalk.cyan('mailmodo login')} to re-authenticate.`, response));
66
+ this.error(`Invalid API key. Run ${chalk.cyan('mailmodo login')} to re-authenticate.`);
69
67
  }
70
68
  if (response.status === 429) {
71
- this.error(this.formatApiFailure('Rate limit exceeded. Please try again later.', response));
69
+ this.error('Rate limit exceeded. Please try again later.');
72
70
  }
73
- this.error(this.formatApiFailure(response.error || 'An unexpected API error occurred.', response));
74
- }
75
- /**
76
- * Builds the terminal error string for a failed API call, appending request
77
- * metadata when {@link ApiRequestDebugInfo} is available.
78
- *
79
- * @param {string} message - Primary error text (HTTP message or generic).
80
- * @param {{ status: number; debug?: ApiRequestDebugInfo }} response - Failed API response.
81
- * @returns {string} Message plus indented Request details for troubleshooting.
82
- */
83
- formatApiFailure(message, response) {
84
- const { debug, status } = response;
85
- if (!debug) {
86
- return message;
87
- }
88
- return [
89
- message,
90
- '',
91
- chalk.dim('Request:'),
92
- chalk.dim(` URL: ${debug.fullUrl}`),
93
- status > 0
94
- ? chalk.dim(` Status: ${String(status)}`)
95
- : chalk.dim(' Status: (No HTTP response — network or client error)'),
96
- ...(debug.responseSummary
97
- ? [chalk.dim(` Response: ${debug.responseSummary}`)]
98
- : []),
99
- ...(debug.causeCode
100
- ? [chalk.dim(` Error Code: ${debug.causeCode}`)]
101
- : []),
102
- ].join('\n');
71
+ this.error(response.error || 'An unexpected API error occurred.');
103
72
  }
104
73
  }
@@ -1,22 +1,22 @@
1
1
  export declare const API_BASE_URL: string;
2
2
  export declare const API_ENDPOINTS: Readonly<{
3
- ANALYTICS: "/api/analytics";
4
- ANALYZE: "/api/analyze";
5
- ASSETS_LOGO: "/api/assets/logo";
6
- AUTH_VALIDATE: "/api/auth/validate";
7
- BILLING_CAP: "/api/billing/cap";
8
- BILLING_STATUS: "/api/billing/status";
9
- CONTACTS: "/api/contacts";
10
- CONTACTS_EXPORT: "/api/contacts/export";
11
- DOMAIN: "/api/domain";
12
- DOMAIN_STATUS: "/api/domain/status";
13
- DOMAIN_VERIFY: "/api/domain/verify";
14
- EDIT: "/api/edit";
15
- EVENTS: "/api/events";
16
- GENERATE: "/api/generate";
17
- LOGS: "/api/logs";
18
- PREVIEW: "/api/preview";
19
- SEQUENCES: "/api/sequences";
3
+ ANALYTICS: "/analytics";
4
+ ANALYZE: "/analyze";
5
+ ASSETS_LOGO: "/assets/logo";
6
+ AUTH_VALIDATE: "/auth/validate";
7
+ BILLING_CAP: "/billing/cap";
8
+ BILLING_STATUS: "/billing/status";
9
+ CONTACTS: "/contacts";
10
+ CONTACTS_EXPORT: "/contacts/export";
11
+ DOMAIN: "/domain";
12
+ DOMAIN_STATUS: "/domain/status";
13
+ DOMAIN_VERIFY: "/domain/verify";
14
+ EDIT: "/edit";
15
+ EVENTS: "/events";
16
+ GENERATE: "/generate";
17
+ LOGS: "/logs";
18
+ PREVIEW: "/preview";
19
+ SEQUENCES: "/sequences";
20
20
  }>;
21
21
  export declare const SIGNUP_URL = "https://mailmodo.com/cli";
22
22
  export declare const DOCS_URL = "https://mailmodo.com/docs/cli";
@@ -1,27 +1,22 @@
1
- /** Set by `bin/dev.js` when running the CLI locally (tsx bootstrap). */
2
- const DEV_API_BASE_URL = 'https://app-vertex-debug.azurewebsites.net';
3
- const PRODUCTION_API_BASE_URL = 'https://api.mailmodo.com';
4
- export const API_BASE_URL = process.env.MAILMODO_DEV_TSX
5
- ? DEV_API_BASE_URL
6
- : PRODUCTION_API_BASE_URL;
1
+ export const API_BASE_URL = process.env.MAILMODO_API_URL || 'https://api.mailmodo.com/cli/v1';
7
2
  export const API_ENDPOINTS = Object.freeze({
8
- ANALYTICS: '/api/analytics',
9
- ANALYZE: '/api/analyze',
10
- ASSETS_LOGO: '/api/assets/logo',
11
- AUTH_VALIDATE: '/api/auth/validate',
12
- BILLING_CAP: '/api/billing/cap',
13
- BILLING_STATUS: '/api/billing/status',
14
- CONTACTS: '/api/contacts',
15
- CONTACTS_EXPORT: '/api/contacts/export',
16
- DOMAIN: '/api/domain',
17
- DOMAIN_STATUS: '/api/domain/status',
18
- DOMAIN_VERIFY: '/api/domain/verify',
19
- EDIT: '/api/edit',
20
- EVENTS: '/api/events',
21
- GENERATE: '/api/generate',
22
- LOGS: '/api/logs',
23
- PREVIEW: '/api/preview',
24
- SEQUENCES: '/api/sequences',
3
+ ANALYTICS: '/analytics',
4
+ ANALYZE: '/analyze',
5
+ ASSETS_LOGO: '/assets/logo',
6
+ AUTH_VALIDATE: '/auth/validate',
7
+ BILLING_CAP: '/billing/cap',
8
+ BILLING_STATUS: '/billing/status',
9
+ CONTACTS: '/contacts',
10
+ CONTACTS_EXPORT: '/contacts/export',
11
+ DOMAIN: '/domain',
12
+ DOMAIN_STATUS: '/domain/status',
13
+ DOMAIN_VERIFY: '/domain/verify',
14
+ EDIT: '/edit',
15
+ EVENTS: '/events',
16
+ GENERATE: '/generate',
17
+ LOGS: '/logs',
18
+ PREVIEW: '/preview',
19
+ SEQUENCES: '/sequences',
25
20
  });
26
21
  export const SIGNUP_URL = 'https://mailmodo.com/cli';
27
22
  export const DOCS_URL = 'https://mailmodo.com/docs/cli';
@@ -342,15 +342,13 @@
342
342
  "index.js"
343
343
  ]
344
344
  },
345
- "logs": {
345
+ "login": {
346
346
  "aliases": [],
347
347
  "args": {},
348
- "description": "View email send logs and delivery events",
348
+ "description": "Authenticate with Mailmodo using your API key",
349
349
  "examples": [
350
- "<%= config.bin %> logs",
351
- "<%= config.bin %> logs --email sarah@example.com",
352
- "<%= config.bin %> logs --failed",
353
- "<%= config.bin %> logs --json"
350
+ "<%= config.bin %> login",
351
+ "MAILMODO_API_KEY=mm_live_xxx <%= config.bin %> login"
354
352
  ],
355
353
  "flags": {
356
354
  "json": {
@@ -365,24 +363,11 @@
365
363
  "name": "yes",
366
364
  "allowNo": false,
367
365
  "type": "boolean"
368
- },
369
- "email": {
370
- "description": "Filter logs by contact email",
371
- "name": "email",
372
- "hasDynamicHelp": false,
373
- "multiple": false,
374
- "type": "option"
375
- },
376
- "failed": {
377
- "description": "Show only failed/bounced events",
378
- "name": "failed",
379
- "allowNo": false,
380
- "type": "boolean"
381
366
  }
382
367
  },
383
368
  "hasDynamicHelp": false,
384
369
  "hiddenAliases": [],
385
- "id": "logs",
370
+ "id": "login",
386
371
  "pluginAlias": "@mailmodo/cli",
387
372
  "pluginName": "@mailmodo/cli",
388
373
  "pluginType": "core",
@@ -392,17 +377,19 @@
392
377
  "relativePath": [
393
378
  "dist",
394
379
  "commands",
395
- "logs",
380
+ "login",
396
381
  "index.js"
397
382
  ]
398
383
  },
399
- "login": {
384
+ "logs": {
400
385
  "aliases": [],
401
386
  "args": {},
402
- "description": "Authenticate with Mailmodo using your API key",
387
+ "description": "View email send logs and delivery events",
403
388
  "examples": [
404
- "<%= config.bin %> login",
405
- "MAILMODO_API_KEY=mm_live_xxx <%= config.bin %> login"
389
+ "<%= config.bin %> logs",
390
+ "<%= config.bin %> logs --email sarah@example.com",
391
+ "<%= config.bin %> logs --failed",
392
+ "<%= config.bin %> logs --json"
406
393
  ],
407
394
  "flags": {
408
395
  "json": {
@@ -417,11 +404,24 @@
417
404
  "name": "yes",
418
405
  "allowNo": false,
419
406
  "type": "boolean"
407
+ },
408
+ "email": {
409
+ "description": "Filter logs by contact email",
410
+ "name": "email",
411
+ "hasDynamicHelp": false,
412
+ "multiple": false,
413
+ "type": "option"
414
+ },
415
+ "failed": {
416
+ "description": "Show only failed/bounced events",
417
+ "name": "failed",
418
+ "allowNo": false,
419
+ "type": "boolean"
420
420
  }
421
421
  },
422
422
  "hasDynamicHelp": false,
423
423
  "hiddenAliases": [],
424
- "id": "login",
424
+ "id": "logs",
425
425
  "pluginAlias": "@mailmodo/cli",
426
426
  "pluginName": "@mailmodo/cli",
427
427
  "pluginType": "core",
@@ -431,18 +431,23 @@
431
431
  "relativePath": [
432
432
  "dist",
433
433
  "commands",
434
- "login",
434
+ "logs",
435
435
  "index.js"
436
436
  ]
437
437
  },
438
- "settings": {
438
+ "preview": {
439
439
  "aliases": [],
440
- "args": {},
441
- "description": "View and update project settings",
440
+ "args": {
441
+ "id": {
442
+ "description": "Email ID to preview",
443
+ "name": "id"
444
+ }
445
+ },
446
+ "description": "Preview an email in browser, as text, or send a test",
442
447
  "examples": [
443
- "<%= config.bin %> settings",
444
- "<%= config.bin %> settings --set brand_color=#0F3460",
445
- "<%= config.bin %> settings --json"
448
+ "<%= config.bin %> preview welcome",
449
+ "<%= config.bin %> preview welcome --text",
450
+ "<%= config.bin %> preview welcome --send me@example.com"
446
451
  ],
447
452
  "flags": {
448
453
  "json": {
@@ -458,17 +463,23 @@
458
463
  "allowNo": false,
459
464
  "type": "boolean"
460
465
  },
461
- "set": {
462
- "description": "Set a setting (format: key=value)",
463
- "name": "set",
466
+ "send": {
467
+ "description": "Send test email to this address",
468
+ "name": "send",
464
469
  "hasDynamicHelp": false,
465
470
  "multiple": false,
466
471
  "type": "option"
472
+ },
473
+ "text": {
474
+ "description": "Output plain text version (for AI agents)",
475
+ "name": "text",
476
+ "allowNo": false,
477
+ "type": "boolean"
467
478
  }
468
479
  },
469
480
  "hasDynamicHelp": false,
470
481
  "hiddenAliases": [],
471
- "id": "settings",
482
+ "id": "preview",
472
483
  "pluginAlias": "@mailmodo/cli",
473
484
  "pluginName": "@mailmodo/cli",
474
485
  "pluginType": "core",
@@ -478,23 +489,18 @@
478
489
  "relativePath": [
479
490
  "dist",
480
491
  "commands",
481
- "settings",
492
+ "preview",
482
493
  "index.js"
483
494
  ]
484
495
  },
485
- "preview": {
496
+ "settings": {
486
497
  "aliases": [],
487
- "args": {
488
- "id": {
489
- "description": "Email ID to preview",
490
- "name": "id"
491
- }
492
- },
493
- "description": "Preview an email in browser, as text, or send a test",
498
+ "args": {},
499
+ "description": "View and update project settings",
494
500
  "examples": [
495
- "<%= config.bin %> preview welcome",
496
- "<%= config.bin %> preview welcome --text",
497
- "<%= config.bin %> preview welcome --send me@example.com"
501
+ "<%= config.bin %> settings",
502
+ "<%= config.bin %> settings --set brand_color=#0F3460",
503
+ "<%= config.bin %> settings --json"
498
504
  ],
499
505
  "flags": {
500
506
  "json": {
@@ -510,23 +516,17 @@
510
516
  "allowNo": false,
511
517
  "type": "boolean"
512
518
  },
513
- "send": {
514
- "description": "Send test email to this address",
515
- "name": "send",
519
+ "set": {
520
+ "description": "Set a setting (format: key=value)",
521
+ "name": "set",
516
522
  "hasDynamicHelp": false,
517
523
  "multiple": false,
518
524
  "type": "option"
519
- },
520
- "text": {
521
- "description": "Output plain text version (for AI agents)",
522
- "name": "text",
523
- "allowNo": false,
524
- "type": "boolean"
525
525
  }
526
526
  },
527
527
  "hasDynamicHelp": false,
528
528
  "hiddenAliases": [],
529
- "id": "preview",
529
+ "id": "settings",
530
530
  "pluginAlias": "@mailmodo/cli",
531
531
  "pluginName": "@mailmodo/cli",
532
532
  "pluginType": "core",
@@ -536,7 +536,7 @@
536
536
  "relativePath": [
537
537
  "dist",
538
538
  "commands",
539
- "preview",
539
+ "settings",
540
540
  "index.js"
541
541
  ]
542
542
  },
@@ -580,5 +580,5 @@
580
580
  ]
581
581
  }
582
582
  },
583
- "version": "0.0.8-beta.pr10.14"
583
+ "version": "0.0.8"
584
584
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mailmodo/cli",
3
3
  "description": "Email lifecycle automation for the AI-native builder generation.",
4
- "version": "0.0.8-beta.pr10.14",
4
+ "version": "0.0.8",
5
5
  "author": "provishalk",
6
6
  "bin": {
7
7
  "mailmodo": "bin/run.js"