@mailmodo/cli 0.0.55 → 0.0.56-beta.pr58.101

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 (138) hide show
  1. package/dist/commands/billing/index.d.ts +1 -11
  2. package/dist/commands/billing/index.js +28 -184
  3. package/dist/commands/contacts/index.d.ts +1 -19
  4. package/dist/commands/contacts/index.js +21 -114
  5. package/dist/commands/deploy/index.js +12 -7
  6. package/dist/commands/deployments/index.d.ts +1 -4
  7. package/dist/commands/deployments/index.js +11 -52
  8. package/dist/commands/domain/index.d.ts +1 -14
  9. package/dist/commands/domain/index.js +19 -100
  10. package/dist/commands/edit/index.d.ts +2 -20
  11. package/dist/commands/edit/index.js +33 -258
  12. package/dist/commands/emails/index.d.ts +1 -2
  13. package/dist/commands/emails/index.js +26 -91
  14. package/dist/commands/init/index.d.ts +1 -3
  15. package/dist/commands/init/index.js +51 -200
  16. package/dist/commands/login/index.d.ts +2 -0
  17. package/dist/commands/login/index.js +32 -79
  18. package/dist/commands/logs/index.d.ts +1 -8
  19. package/dist/commands/logs/index.js +12 -55
  20. package/dist/commands/preview/index.d.ts +1 -19
  21. package/dist/commands/preview/index.js +32 -212
  22. package/dist/commands/sdk/index.d.ts +1 -3
  23. package/dist/commands/sdk/index.js +14 -46
  24. package/dist/commands/settings/index.d.ts +1 -22
  25. package/dist/commands/settings/index.js +34 -246
  26. package/dist/commands/status/index.d.ts +1 -0
  27. package/dist/commands/status/index.js +13 -39
  28. package/dist/lib/base-command.d.ts +38 -10
  29. package/dist/lib/base-command.js +171 -18
  30. package/dist/lib/commands/billing/checkout-status.d.ts +3 -0
  31. package/dist/lib/commands/billing/checkout-status.js +63 -0
  32. package/dist/lib/commands/billing/format.d.ts +7 -0
  33. package/dist/lib/commands/billing/format.js +63 -0
  34. package/dist/lib/commands/billing/purchase-cap.d.ts +7 -0
  35. package/dist/lib/commands/billing/purchase-cap.js +57 -0
  36. package/dist/lib/commands/billing/types.d.ts +72 -0
  37. package/dist/lib/commands/contacts/actions.d.ts +3 -0
  38. package/dist/lib/commands/contacts/actions.js +49 -0
  39. package/dist/lib/commands/contacts/export-delete.d.ts +9 -0
  40. package/dist/lib/commands/contacts/export-delete.js +51 -0
  41. package/dist/lib/commands/contacts/types.d.ts +35 -0
  42. package/dist/lib/commands/contacts/types.js +1 -0
  43. package/dist/lib/{deploy → commands/deploy}/domain-setup.d.ts +1 -1
  44. package/dist/lib/{deploy → commands/deploy}/domain-setup.js +2 -2
  45. package/dist/lib/{deploy → commands/deploy}/output.d.ts +1 -1
  46. package/dist/lib/{deploy → commands/deploy}/output.js +2 -2
  47. package/dist/lib/{deploy → commands/deploy}/payload.d.ts +1 -1
  48. package/dist/lib/{deploy → commands/deploy}/payload.js +2 -2
  49. package/dist/lib/{deploy → commands/deploy}/sequence-status.js +2 -2
  50. package/dist/lib/{deploy → commands/deploy}/types.d.ts +4 -4
  51. package/dist/lib/commands/deploy/types.js +1 -0
  52. package/dist/lib/commands/deployments/output.d.ts +2 -0
  53. package/dist/lib/commands/deployments/output.js +68 -0
  54. package/dist/lib/commands/deployments/types.d.ts +24 -0
  55. package/dist/lib/commands/deployments/types.js +1 -0
  56. package/dist/lib/commands/domain/setup.d.ts +8 -0
  57. package/dist/lib/commands/domain/setup.js +53 -0
  58. package/dist/lib/commands/domain/types.d.ts +56 -0
  59. package/dist/lib/commands/domain/types.js +1 -0
  60. package/dist/lib/commands/domain/verify.d.ts +5 -0
  61. package/dist/lib/commands/domain/verify.js +50 -0
  62. package/dist/lib/commands/edit/diff.d.ts +7 -0
  63. package/dist/lib/commands/edit/diff.js +65 -0
  64. package/dist/lib/commands/edit/display.d.ts +5 -0
  65. package/dist/lib/commands/edit/display.js +53 -0
  66. package/dist/lib/commands/edit/flow.d.ts +8 -0
  67. package/dist/lib/commands/edit/flow.js +70 -0
  68. package/dist/lib/commands/edit/persist.d.ts +5 -0
  69. package/dist/lib/commands/edit/persist.js +67 -0
  70. package/dist/lib/commands/edit/types.d.ts +38 -0
  71. package/dist/lib/commands/edit/types.js +1 -0
  72. package/dist/lib/commands/emails/editor.d.ts +2 -0
  73. package/dist/lib/commands/emails/editor.js +43 -0
  74. package/dist/lib/commands/emails/output.d.ts +4 -0
  75. package/dist/lib/commands/emails/output.js +36 -0
  76. package/dist/lib/commands/emails/types.d.ts +3 -0
  77. package/dist/lib/commands/emails/types.js +1 -0
  78. package/dist/lib/commands/init/analysis.d.ts +3 -0
  79. package/dist/lib/commands/init/analysis.js +73 -0
  80. package/dist/lib/commands/init/output.d.ts +12 -0
  81. package/dist/lib/commands/init/output.js +39 -0
  82. package/dist/lib/commands/init/payload.d.ts +8 -0
  83. package/dist/lib/commands/init/payload.js +78 -0
  84. package/dist/lib/commands/init/types.d.ts +57 -0
  85. package/dist/lib/commands/init/types.js +1 -0
  86. package/dist/lib/commands/login/output.d.ts +8 -0
  87. package/dist/lib/commands/login/output.js +40 -0
  88. package/dist/lib/commands/login/types.d.ts +19 -0
  89. package/dist/lib/commands/login/types.js +1 -0
  90. package/dist/lib/commands/logs/output.d.ts +2 -0
  91. package/dist/lib/commands/logs/output.js +52 -0
  92. package/dist/lib/commands/logs/types.d.ts +23 -0
  93. package/dist/lib/commands/logs/types.js +1 -0
  94. package/dist/lib/commands/preview/actions.d.ts +11 -0
  95. package/dist/lib/commands/preview/actions.js +43 -0
  96. package/dist/lib/commands/preview/render.d.ts +3 -0
  97. package/dist/lib/commands/preview/render.js +30 -0
  98. package/dist/lib/commands/preview/server.d.ts +8 -0
  99. package/dist/lib/commands/preview/server.js +63 -0
  100. package/dist/lib/commands/preview/types.d.ts +22 -0
  101. package/dist/lib/commands/preview/types.js +1 -0
  102. package/dist/lib/commands/preview/wrapper-html.d.ts +2 -0
  103. package/dist/lib/commands/preview/wrapper-html.js +35 -0
  104. package/dist/lib/commands/sdk/output.d.ts +2 -0
  105. package/dist/lib/commands/sdk/output.js +42 -0
  106. package/dist/lib/commands/sdk/types.d.ts +21 -0
  107. package/dist/lib/commands/sdk/types.js +1 -0
  108. package/dist/lib/commands/settings/actions.d.ts +10 -0
  109. package/dist/lib/commands/settings/actions.js +56 -0
  110. package/dist/lib/commands/settings/display.d.ts +15 -0
  111. package/dist/lib/commands/settings/display.js +69 -0
  112. package/dist/lib/commands/settings/logo-domain.d.ts +3 -0
  113. package/dist/lib/commands/settings/logo-domain.js +47 -0
  114. package/dist/lib/commands/settings/prompt.d.ts +2 -0
  115. package/dist/lib/commands/settings/prompt.js +82 -0
  116. package/dist/lib/commands/settings/types.d.ts +65 -0
  117. package/dist/lib/commands/settings/types.js +1 -0
  118. package/dist/lib/commands/status/output.d.ts +2 -0
  119. package/dist/lib/commands/status/output.js +49 -0
  120. package/dist/lib/commands/status/types.d.ts +28 -0
  121. package/dist/lib/commands/status/types.js +1 -0
  122. package/dist/lib/constants.d.ts +3 -2
  123. package/dist/lib/constants.js +4 -5
  124. package/dist/lib/messages.d.ts +11 -0
  125. package/dist/lib/messages.js +31 -0
  126. package/dist/lib/templates/missing-templates.d.ts +16 -2
  127. package/dist/lib/templates/missing-templates.js +34 -22
  128. package/dist/lib/templates/regenerate.d.ts +10 -0
  129. package/dist/lib/templates/regenerate.js +29 -0
  130. package/dist/lib/templates/sync.d.ts +33 -0
  131. package/dist/lib/templates/sync.js +106 -0
  132. package/dist/lib/templates/types.d.ts +3 -0
  133. package/dist/lib/yaml-config.d.ts +1 -0
  134. package/dist/lib/yaml-config.js +8 -0
  135. package/oclif.manifest.json +100 -100
  136. package/package.json +1 -1
  137. /package/dist/lib/{deploy → commands/billing}/types.js +0 -0
  138. /package/dist/lib/{deploy → commands/deploy}/sequence-status.d.ts +0 -0
@@ -1,57 +1,10 @@
1
1
  import { Args, Flags } from '@oclif/core';
2
- import { createServer } from 'node:http';
3
- import chalk from 'chalk';
4
- import open from 'open';
5
2
  import { BaseCommand } from '../../lib/base-command.js';
6
- import { API_ENDPOINTS, PREVIEW_PORT } from '../../lib/constants.js';
7
- import { INFO } from '../../lib/messages.js';
8
- import { loadTemplate, getEmailStyle, getTemplateFilename, } from '../../lib/yaml-config.js';
9
3
  import { handleMissingTemplates } from '../../lib/templates/missing-templates.js';
10
- /* eslint-disable camelcase */
11
- const SAMPLE_DATA = Object.freeze({
12
- app_url: 'https://yourapp.com',
13
- cta_url: 'https://yourapp.com/action',
14
- first_name: 'Sarah',
15
- product_name: 'YourApp',
16
- unsubscribe_url: '#',
17
- });
18
- /* eslint-enable camelcase */
19
- /**
20
- * Replaces all {{variable}} template placeholders in the HTML string
21
- * with corresponding values from the sample data map.
22
- *
23
- * @param {string} html - Raw HTML template with {{variable}} placeholders.
24
- * @param {Record<string, string>} data - Key-value map of template variable replacements.
25
- * @returns {string} HTML with all recognized placeholders replaced.
26
- */
27
- function renderTemplate(html, data) {
28
- return html.replaceAll(/\{\{(\w+)\}\}/g, (match, key) => data[key] || match);
29
- }
30
- /**
31
- * Strips HTML tags and decodes common HTML entities to produce
32
- * a readable plain text representation of an email.
33
- *
34
- * @param {string} html - The HTML content to convert.
35
- * @returns {string} Plain text with tags removed and entities decoded.
36
- */
37
- function htmlToText(html) {
38
- return html
39
- .replaceAll(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
40
- .replaceAll(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
41
- .replaceAll(/<br\s*\/?>/gi, '\n')
42
- .replaceAll(/<\/p>/gi, '\n\n')
43
- .replaceAll(/<\/div>/gi, '\n')
44
- .replaceAll(/<\/tr>/gi, '\n')
45
- .replaceAll(/<\/li>/gi, '\n')
46
- .replaceAll(/<[^>]+>/g, '')
47
- .replaceAll('&nbsp;', ' ')
48
- .replaceAll('&amp;', '&')
49
- .replaceAll('&lt;', '<')
50
- .replaceAll('&gt;', '>')
51
- .replaceAll('&quot;', '"')
52
- .replaceAll(/\n{3,}/g, '\n\n')
53
- .trim();
54
- }
4
+ import { loadTemplate, getEmailStyle, getTemplateFilename, } from '../../lib/yaml-config.js';
5
+ import { renderText, sendTestEmail, } from '../../lib/commands/preview/actions.js';
6
+ import { SAMPLE_DATA, renderTemplate, } from '../../lib/commands/preview/render.js';
7
+ import { startPreviewServer } from '../../lib/commands/preview/server.js';
55
8
  export default class Preview extends BaseCommand {
56
9
  static args = {
57
10
  id: Args.string({ description: 'Email template ID to preview' }),
@@ -81,34 +34,29 @@ export default class Preview extends BaseCommand {
81
34
  if (!email) {
82
35
  this.error(`Template '${templateId}' not found in mailmodo.yaml.`);
83
36
  }
37
+ /* eslint-disable camelcase */
84
38
  const sampleData = {
85
39
  ...SAMPLE_DATA,
86
- app_url: yamlConfig.project?.url || 'https://yourapp.com', // eslint-disable-line camelcase
87
- product_name: yamlConfig.project?.name || 'YourApp', // eslint-disable-line camelcase
40
+ app_url: yamlConfig.project?.url || 'https://yourapp.com',
41
+ product_name: yamlConfig.project?.name || 'YourApp',
88
42
  };
43
+ /* eslint-enable camelcase */
89
44
  const effectiveStyle = getEmailStyle(email.style, yamlConfig.project?.emailStyle);
90
45
  const templateFilename = getTemplateFilename(email.id, email.style, yamlConfig.project?.emailStyle);
91
46
  let templateHtml = await loadTemplate(templateFilename);
92
47
  if (!templateHtml) {
93
48
  await this.ensureAuth();
94
- const regenCtx = {
95
- error: (msg) => this.error(msg),
96
- exit: (code) => this.exit(code),
97
- log: (msg) => this.log(msg),
98
- onApiError: (r) => this.handleApiError(r),
99
- post: (path, body) => this.apiClient.post(path, body),
100
- spinner: (text, json, work) => this.withApiSpinner({ json, text }, work),
101
- syncYaml: () => this.syncYamlToServer(),
102
- };
103
- const regenerated = await handleMissingTemplates(regenCtx, yamlConfig, [email.id], { json: flags.json, yes: flags.yes });
49
+ const regenerated = await handleMissingTemplates(this.makeCtx(), yamlConfig, [email.id], { json: flags.json, yes: flags.yes });
104
50
  if (!regenerated)
105
51
  return;
106
52
  templateHtml = await loadTemplate(templateFilename);
107
53
  if (!templateHtml)
108
54
  this.error('Template regeneration failed.');
109
55
  }
56
+ const ctx = this.makeCtx();
110
57
  if (flags.send) {
111
- await this.sendTestEmail(email, renderTemplate(templateHtml, sampleData), {
58
+ await this.ensureAuth();
59
+ await sendTestEmail(ctx, email, renderTemplate(templateHtml, sampleData), {
112
60
  domain: yamlConfig.project?.domain,
113
61
  jsonOutput: flags.json,
114
62
  toAddress: flags.send,
@@ -116,159 +64,31 @@ export default class Preview extends BaseCommand {
116
64
  return;
117
65
  }
118
66
  if (flags.text) {
119
- await this.renderText(email, templateHtml, sampleData, flags.json);
67
+ await renderText(ctx, email, {
68
+ jsonOutput: flags.json,
69
+ sampleData,
70
+ templateHtml,
71
+ });
120
72
  return;
121
73
  }
122
- await this.startPreviewServer(email, templateHtml, sampleData, {
74
+ await startPreviewServer(ctx, email, {
123
75
  effectiveStyle,
124
76
  jsonOutput: flags.json,
77
+ rendered: renderTemplate(templateHtml, sampleData),
78
+ sampleData,
125
79
  });
126
80
  }
127
- /**
128
- * Renders a plain text version of the email to stdout. Used by AI agents
129
- * and CI pipelines that cannot open a browser.
130
- */
131
- async renderText(email, templateHtml, sampleData, jsonOutput) {
132
- const plainText = htmlToText(renderTemplate(templateHtml, sampleData));
133
- if (jsonOutput) {
134
- this.log(JSON.stringify({
135
- body: plainText,
136
- id: email.id,
137
- previewText: email.previewText,
138
- subject: email.subject,
139
- }, null, 2));
140
- return;
141
- }
142
- this.log(`\n ${chalk.bold('SUBJECT:')} ${email.subject}`);
143
- if (email.previewText) {
144
- this.log(` ${chalk.bold('PREVIEW:')} ${email.previewText}`);
145
- }
146
- this.log(`\n${plainText}\n`);
147
- }
148
- /**
149
- * Calls the API to send a test email to the specified address.
150
- * Before domain verification, tests send via the mailmodo.com domain.
151
- */
152
- async sendTestEmail(email, html, opts) {
153
- const { domain, jsonOutput, toAddress } = opts;
154
- await this.ensureAuth();
155
- const response = await this.withApiSpinner({ json: jsonOutput, text: ' Sending test email...' }, () => this.apiClient.post(`${API_ENDPOINTS.PREVIEW}/send`, {
156
- domain,
157
- html,
158
- subject: email.subject,
159
- to: toAddress,
160
- }));
161
- if (!response.ok) {
162
- this.handleApiError(response);
163
- }
164
- const { note, sentTo, sentVia, status } = response.data;
165
- if (jsonOutput) {
166
- this.log(JSON.stringify({ note, sentTo, sentVia, status }, null, 2));
167
- return;
168
- }
169
- this.log(`\n ${chalk.green('✓')} Test email sent to ${chalk.cyan(sentTo)} via ${chalk.cyan(sentVia)}.`);
170
- if (note) {
171
- this.log(` ${chalk.dim(note)}`);
172
- }
173
- this.log('');
174
- }
175
- /**
176
- * Probes ports starting at startPort and returns the first one not in use.
177
- */
178
- async findAvailablePort(startPort, endPort = startPort + 10) {
179
- if (startPort > endPort) {
180
- throw new Error(`No available port found starting from port ${endPort - 10}`);
181
- }
182
- const available = await new Promise((resolve) => {
183
- const probe = createServer();
184
- probe.once('error', () => resolve(false));
185
- probe.once('listening', () => probe.close(() => resolve(true)));
186
- probe.listen(startPort);
187
- });
188
- return available
189
- ? startPort
190
- : this.findAvailablePort(startPort + 1, endPort);
191
- }
192
- /**
193
- * Starts a local HTTP server to serve the rendered email template,
194
- * then opens the user's default browser to view it.
195
- */
196
- async startPreviewServer(email, templateHtml, sampleData, opts) {
197
- const { effectiveStyle, jsonOutput } = opts;
198
- const rendered = renderTemplate(templateHtml, sampleData);
199
- const wrapperHtml = `<!DOCTYPE html>
200
- <html>
201
- <head>
202
- <meta charset="utf-8">
203
- <title>Preview: ${email.subject}</title>
204
- <style>
205
- body { margin: 0; padding: 2rem; background: #f5f5f5; font-family: system-ui; }
206
- .preview-bar { background: #1a1a2e; color: #fff; padding: 0.75rem 1.5rem; border-radius: 0.5rem;
207
- margin-bottom: 1.5rem; display: flex; justify-content: space-between; align-items: center; }
208
- .preview-bar h3 { margin: 0; font-size: 0.875rem; }
209
- .preview-bar span { font-size: 0.75rem; opacity: 0.7; }
210
- .email-frame { background: #fff; max-width: 40rem; margin: 0 auto; border-radius: 0.5rem;
211
- box-shadow: 0 0.125rem 0.5rem rgba(0,0,0,0.1); overflow: hidden; }
212
- .email-header { padding: 1rem 1.5rem; border-bottom: 1px solid #eee; }
213
- .email-header .subject { font-weight: 600; font-size: 1rem; }
214
- .email-header .meta { font-size: 0.75rem; color: #666; margin-top: 0.25rem; }
215
- .email-body { padding: 1.5rem; }
216
- </style>
217
- </head>
218
- <body>
219
- <div class="preview-bar">
220
- <h3>Mailmodo Preview — ${email.id}</h3>
221
- <span>Style: ${effectiveStyle} · Press Ctrl+C in terminal to stop</span>
222
- </div>
223
- <div class="email-frame">
224
- <div class="email-header">
225
- <div class="subject">${email.subject}</div>
226
- <div class="meta">To: ${sampleData.first_name} · From: ${sampleData.product_name}</div>
227
- </div>
228
- <div class="email-body">${rendered}</div>
229
- </div>
230
- </body>
231
- </html>`;
232
- const port = await this.findAvailablePort(PREVIEW_PORT);
233
- if (!jsonOutput && port !== PREVIEW_PORT) {
234
- this.log(`\n ${chalk.yellow('!')} Port ${PREVIEW_PORT} is already in use. Opening preview on port ${chalk.cyan(String(port))}.`);
235
- }
236
- if (jsonOutput) {
237
- this.log(JSON.stringify({
238
- id: email.id,
239
- style: effectiveStyle,
240
- url: `http://localhost:${port}`,
241
- }, null, 2));
242
- }
243
- const server = createServer((_req, res) => {
244
- res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
245
- res.end(wrapperHtml);
246
- });
247
- await new Promise((resolve) => {
248
- server.listen(port, () => resolve());
249
- });
250
- const url = `http://localhost:${port}`;
251
- if (!jsonOutput) {
252
- this.log(`\n Style: ${chalk.cyan(effectiveStyle)}`);
253
- this.log(` Preview server at ${chalk.cyan(url)}`);
254
- this.log(` ${INFO.BROWSER_OPENING}\n`);
255
- this.log(` ${chalk.dim('Press Ctrl+C to stop the preview server.')}\n`);
256
- }
257
- try {
258
- await open(url);
259
- }
260
- catch {
261
- if (!jsonOutput) {
262
- this.log(` ${INFO.BROWSER_OPEN_FAILED}`);
263
- }
264
- }
265
- await new Promise((resolve) => {
266
- const shutdown = () => {
267
- server.closeAllConnections?.();
268
- server.close(() => resolve());
269
- };
270
- process.once('SIGINT', shutdown);
271
- process.once('SIGTERM', shutdown);
272
- });
81
+ makeCtx() {
82
+ return {
83
+ error: (msg) => this.error(msg),
84
+ exit: (code) => this.exit(code),
85
+ fetchTemplate: (emailId) => this.getTemplateFromServer(emailId),
86
+ log: (msg) => this.log(msg),
87
+ onApiError: (r) => this.handleApiError(r),
88
+ post: (path, body) => this.apiClient.post(path, body),
89
+ spinner: (text, json, work) => this.withApiSpinner({ json, text }, work),
90
+ syncTemplates: (yaml) => this.syncTemplatesToServer(yaml),
91
+ syncYaml: () => this.syncYamlToServer(),
92
+ };
273
93
  }
274
94
  }
@@ -8,7 +8,5 @@ export default class Sdk extends BaseCommand {
8
8
  yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
9
  };
10
10
  run(): Promise<void>;
11
- private renderSnippets;
12
- private renderSequenceBlock;
13
- private renderCallBlock;
11
+ private makeCtx;
14
12
  }
@@ -1,8 +1,7 @@
1
1
  import { Flags } from '@oclif/core';
2
- import chalk from 'chalk';
3
2
  import { BaseCommand } from '../../lib/base-command.js';
4
- import { API_ENDPOINTS, SDK_IMPORT_SNIPPET, SDK_INSTALL_COMMAND, } from '../../lib/constants.js';
5
- import { SEPARATOR } from '../../lib/messages.js';
3
+ import { API_ENDPOINTS } from '../../lib/constants.js';
4
+ import { renderSdkSnippets } from '../../lib/commands/sdk/output.js';
6
5
  export default class Sdk extends BaseCommand {
7
6
  static description = 'Show the SDK track() / identify() reference for deployed sequences';
8
7
  static examples = [
@@ -19,56 +18,25 @@ export default class Sdk extends BaseCommand {
19
18
  async run() {
20
19
  const { flags } = await this.parse(Sdk);
21
20
  await this.ensureAuth();
21
+ const ctx = this.makeCtx();
22
22
  const params = flags['sequence-id']
23
23
  ? { sequenceId: flags['sequence-id'] }
24
24
  : undefined;
25
- const response = await this.withApiSpinner({ json: flags.json, text: ' Loading SDK reference...' }, () => this.apiClient.get(API_ENDPOINTS.SEQUENCES_SDK, params));
26
- if (!response.ok) {
27
- this.handleApiError(response);
28
- }
25
+ const response = await ctx.spinner(' Loading SDK reference...', flags.json, () => ctx.get(API_ENDPOINTS.SEQUENCES_SDK, params));
26
+ if (!response.ok)
27
+ ctx.onApiError(response);
29
28
  if (flags.json) {
30
29
  this.log(JSON.stringify(response.data, null, 2));
31
30
  return;
32
31
  }
33
- this.renderSnippets(response.data);
34
- }
35
- renderSnippets(data) {
36
- const snippets = data.sdkSnippets ?? [];
37
- if (snippets.length === 0) {
38
- this.log(`\n ${chalk.dim('No active deployed sequences.')}`);
39
- this.log(` Run ${chalk.cyan('mailmodo deploy')} to deploy one.\n`);
40
- return;
41
- }
42
- this.log(`\n ${chalk.bold(String(snippets.length))} active ${snippets.length === 1 ? 'sequence' : 'sequences'}:\n`);
43
- this.log(` ${SEPARATOR}`);
44
- this.log(` ${chalk.bold('SDK EVENT REFERENCE')}`);
45
- this.log(` ${SEPARATOR}\n`);
46
- this.log(` ${chalk.cyan(SDK_INSTALL_COMMAND)}\n`);
47
- this.log(` ${chalk.dim(SDK_IMPORT_SNIPPET)}\n`);
48
- for (const [index, snippet] of snippets.entries()) {
49
- this.renderSequenceBlock(snippet);
50
- if (index < snippets.length - 1)
51
- this.log('');
52
- }
53
- this.log(` ${SEPARATOR}\n`);
32
+ renderSdkSnippets(ctx, response.data);
54
33
  }
55
- renderSequenceBlock(snippet) {
56
- const productName = snippet.productName || 'Unnamed sequence';
57
- this.log(` ${chalk.bold(productName)} ${chalk.dim(`(${snippet.sequenceId})`)}`);
58
- const trackCalls = [...new Set(snippet.sdkSnippet?.trackCalls ?? [])];
59
- const identifyCalls = [...new Set(snippet.sdkSnippet?.identifyCalls ?? [])];
60
- this.renderCallBlock('// track() calls', trackCalls);
61
- this.renderCallBlock('// identify() calls', identifyCalls);
62
- if (trackCalls.length === 0 && identifyCalls.length === 0) {
63
- this.log(` ${chalk.dim('No track() or identify() calls available.')}`);
64
- }
65
- }
66
- renderCallBlock(label, calls) {
67
- if (calls.length === 0)
68
- return;
69
- this.log(` ${chalk.dim(label)}`);
70
- for (const call of calls) {
71
- this.log(` ${chalk.dim(call)}`);
72
- }
34
+ makeCtx() {
35
+ return {
36
+ get: (path, params) => this.apiClient.get(path, params),
37
+ log: (msg) => this.log(msg),
38
+ onApiError: (r) => this.handleApiError(r),
39
+ spinner: (text, json, work) => this.withApiSpinner({ json, text }, work),
40
+ };
73
41
  }
74
42
  }
@@ -8,26 +8,5 @@ export default class Settings extends BaseCommand {
8
8
  yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
9
  };
10
10
  run(): Promise<void>;
11
- private applySetFlag;
12
- private applyMonthlyCapChange;
13
- private displaySettingsGroup;
14
- /**
15
- * Prompts the user to pick a setting key to edit and dispatches
16
- * to the appropriate handler for that key.
17
- */
18
- private promptEditSetting;
19
- /**
20
- * Fetches the domain verification status from the API.
21
- * Returns true/false for verified/unverified, or null if unavailable.
22
- */
23
- private fetchDomainVerified;
24
- private handleDomainChange;
25
- /**
26
- * Handles the logo file upload flow: validates the local file exists,
27
- * reads it, uploads to Mailmodo CDN via API, and updates both logoFile
28
- * and logoUrl in the project config.
29
- *
30
- * @param {import('../../lib/yaml-config.js').MailmodoYaml} yamlConfig - The full YAML config to update and save.
31
- */
32
- private handleLogoUpload;
11
+ private makeCtx;
33
12
  }