@wraps.dev/cli 1.5.1 → 1.5.3

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/cli.js CHANGED
@@ -146,7 +146,7 @@ var require_package = __commonJS({
146
146
  "package.json"(exports, module) {
147
147
  module.exports = {
148
148
  name: "@wraps.dev/cli",
149
- version: "1.5.1",
149
+ version: "1.5.3",
150
150
  description: "CLI for deploying Wraps email infrastructure to your AWS account",
151
151
  type: "module",
152
152
  main: "./dist/cli.js",
@@ -552,7 +552,7 @@ var init_errors = __esm({
552
552
  stackExists: (stackName) => new WrapsError(
553
553
  `Stack "${stackName}" already exists`,
554
554
  "STACK_EXISTS",
555
- `To update: wraps upgrade
555
+ `To update: wraps email upgrade
556
556
  To remove: wraps destroy --stack ${stackName}`,
557
557
  "https://wraps.dev/docs/cli/upgrade"
558
558
  ),
@@ -1592,7 +1592,7 @@ function getPresetInfo(preset) {
1592
1592
  volume: "10k-500k emails/month",
1593
1593
  features: [
1594
1594
  "Everything in Starter",
1595
- "Reputation metrics dashboard",
1595
+ "Reputation tracking",
1596
1596
  "Real-time event tracking (EventBridge)",
1597
1597
  "90-day email history storage",
1598
1598
  "Optional: Email archiving with rendered viewer",
@@ -1801,16 +1801,16 @@ async function promptProvider() {
1801
1801
  const provider = await clack4.select({
1802
1802
  message: "Where is your app hosted?",
1803
1803
  options: [
1804
- {
1805
- value: "vercel",
1806
- label: "Vercel",
1807
- hint: "Uses OIDC (no AWS credentials needed)"
1808
- },
1809
1804
  {
1810
1805
  value: "aws",
1811
1806
  label: "AWS (Lambda/ECS/EC2)",
1812
1807
  hint: "Uses IAM roles automatically"
1813
1808
  },
1809
+ {
1810
+ value: "vercel",
1811
+ label: "Vercel",
1812
+ hint: "Uses OIDC (no AWS credentials needed)"
1813
+ },
1814
1814
  {
1815
1815
  value: "railway",
1816
1816
  label: "Railway",
@@ -2204,6 +2204,14 @@ async function promptEmailArchiving() {
2204
2204
  async function promptCustomConfig(existingConfig) {
2205
2205
  clack4.log.info("Custom configuration builder");
2206
2206
  clack4.log.info("Configure each feature individually");
2207
+ const reputationMetrics = await clack4.confirm({
2208
+ message: "Enable reputation tracking (recommended)?",
2209
+ initialValue: existingConfig?.reputationMetrics ?? true
2210
+ });
2211
+ if (clack4.isCancel(reputationMetrics)) {
2212
+ clack4.cancel("Operation cancelled.");
2213
+ process.exit(0);
2214
+ }
2207
2215
  const trackingEnabled = await clack4.confirm({
2208
2216
  message: "Enable open & click tracking?",
2209
2217
  initialValue: existingConfig?.tracking?.enabled ?? true
@@ -2213,49 +2221,38 @@ async function promptCustomConfig(existingConfig) {
2213
2221
  process.exit(0);
2214
2222
  }
2215
2223
  const eventTrackingEnabled = await clack4.confirm({
2216
- message: "Enable real-time event tracking (EventBridge)?",
2224
+ message: "Store email events in DynamoDB?",
2217
2225
  initialValue: existingConfig?.eventTracking?.enabled ?? true
2218
2226
  });
2219
2227
  if (clack4.isCancel(eventTrackingEnabled)) {
2220
2228
  clack4.cancel("Operation cancelled.");
2221
2229
  process.exit(0);
2222
2230
  }
2223
- let dynamoDBHistory = false;
2224
2231
  let archiveRetention = "90days";
2225
2232
  if (eventTrackingEnabled) {
2226
- dynamoDBHistory = await clack4.confirm({
2227
- message: "Store email history in DynamoDB?",
2228
- initialValue: existingConfig?.eventTracking?.dynamoDBHistory ?? true
2233
+ archiveRetention = await clack4.select({
2234
+ message: "Event history retention period:",
2235
+ options: [
2236
+ { value: "7days", label: "7 days", hint: "Minimal storage cost" },
2237
+ { value: "30days", label: "30 days", hint: "Development/testing" },
2238
+ {
2239
+ value: "90days",
2240
+ label: "90 days (recommended)",
2241
+ hint: "Standard retention"
2242
+ },
2243
+ { value: "1year", label: "1 year", hint: "Compliance requirements" },
2244
+ {
2245
+ value: "indefinite",
2246
+ label: "Indefinite",
2247
+ hint: "Higher storage cost"
2248
+ }
2249
+ ],
2250
+ initialValue: existingConfig?.eventTracking?.archiveRetention || "90days"
2229
2251
  });
2230
- if (clack4.isCancel(dynamoDBHistory)) {
2252
+ if (clack4.isCancel(archiveRetention)) {
2231
2253
  clack4.cancel("Operation cancelled.");
2232
2254
  process.exit(0);
2233
2255
  }
2234
- if (dynamoDBHistory) {
2235
- archiveRetention = await clack4.select({
2236
- message: "Email history retention period:",
2237
- options: [
2238
- { value: "7days", label: "7 days", hint: "Minimal storage cost" },
2239
- { value: "30days", label: "30 days", hint: "Development/testing" },
2240
- {
2241
- value: "90days",
2242
- label: "90 days (recommended)",
2243
- hint: "Standard retention"
2244
- },
2245
- { value: "1year", label: "1 year", hint: "Compliance requirements" },
2246
- {
2247
- value: "indefinite",
2248
- label: "Indefinite",
2249
- hint: "Higher storage cost"
2250
- }
2251
- ],
2252
- initialValue: existingConfig?.eventTracking?.archiveRetention || "90days"
2253
- });
2254
- if (clack4.isCancel(archiveRetention)) {
2255
- clack4.cancel("Operation cancelled.");
2256
- process.exit(0);
2257
- }
2258
- }
2259
2256
  }
2260
2257
  const tlsRequired = await clack4.confirm({
2261
2258
  message: "Require TLS encryption for all emails?",
@@ -2265,14 +2262,40 @@ async function promptCustomConfig(existingConfig) {
2265
2262
  clack4.cancel("Operation cancelled.");
2266
2263
  process.exit(0);
2267
2264
  }
2268
- const reputationMetrics = await clack4.confirm({
2269
- message: "Enable reputation metrics dashboard?",
2270
- initialValue: existingConfig?.reputationMetrics ?? true
2265
+ const customMailFrom = await clack4.confirm({
2266
+ message: "Configure custom MAIL FROM domain? (improves DMARC alignment)",
2267
+ initialValue: existingConfig?.mailFromDomain !== void 0
2271
2268
  });
2272
- if (clack4.isCancel(reputationMetrics)) {
2269
+ if (clack4.isCancel(customMailFrom)) {
2273
2270
  clack4.cancel("Operation cancelled.");
2274
2271
  process.exit(0);
2275
2272
  }
2273
+ let mailFromSubdomain = "mail";
2274
+ if (customMailFrom) {
2275
+ mailFromSubdomain = await clack4.text({
2276
+ message: "MAIL FROM subdomain:",
2277
+ placeholder: "mail",
2278
+ initialValue: existingConfig?.mailFromDomain?.split(".")[0] || "mail",
2279
+ validate: (value) => {
2280
+ if (!value || value.trim() === "") {
2281
+ return "Subdomain is required";
2282
+ }
2283
+ if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/i.test(value)) {
2284
+ return "Invalid subdomain format";
2285
+ }
2286
+ return void 0;
2287
+ }
2288
+ });
2289
+ if (clack4.isCancel(mailFromSubdomain)) {
2290
+ clack4.cancel("Operation cancelled.");
2291
+ process.exit(0);
2292
+ }
2293
+ clack4.log.info(
2294
+ pc5.dim(
2295
+ `MAIL FROM will be set to ${mailFromSubdomain}.yourdomain.com`
2296
+ )
2297
+ );
2298
+ }
2276
2299
  const dedicatedIp = await clack4.confirm({
2277
2300
  message: "Request dedicated IP address? (requires 100k+ emails/day)",
2278
2301
  initialValue: existingConfig?.dedicatedIp ?? false
@@ -2336,6 +2359,7 @@ async function promptCustomConfig(existingConfig) {
2336
2359
  } : { enabled: false },
2337
2360
  tlsRequired,
2338
2361
  reputationMetrics,
2362
+ mailFromSubdomain: customMailFrom ? typeof mailFromSubdomain === "string" ? mailFromSubdomain : "mail" : void 0,
2339
2363
  suppressionList: {
2340
2364
  enabled: true,
2341
2365
  reasons: ["BOUNCE", "COMPLAINT"]
@@ -2353,7 +2377,7 @@ async function promptCustomConfig(existingConfig) {
2353
2377
  "REJECT",
2354
2378
  "RENDERING_FAILURE"
2355
2379
  ],
2356
- dynamoDBHistory: Boolean(dynamoDBHistory),
2380
+ dynamoDBHistory: true,
2357
2381
  archiveRetention: typeof archiveRetention === "string" ? archiveRetention : "90days"
2358
2382
  } : { enabled: false },
2359
2383
  emailArchiving: emailArchivingEnabled ? {
@@ -2694,9 +2718,9 @@ init_esm_shims();
2694
2718
  import { readFileSync } from "fs";
2695
2719
  import { dirname as dirname2, join as join4 } from "path";
2696
2720
  import { fileURLToPath as fileURLToPath4 } from "url";
2697
- import * as clack14 from "@clack/prompts";
2721
+ import * as clack16 from "@clack/prompts";
2698
2722
  import args from "args";
2699
- import pc15 from "picocolors";
2723
+ import pc17 from "picocolors";
2700
2724
 
2701
2725
  // src/commands/dashboard/update-role.ts
2702
2726
  init_esm_shims();
@@ -2987,13 +3011,15 @@ Verification should complete within a few minutes.`,
2987
3011
  )
2988
3012
  ];
2989
3013
  if (domain) {
3014
+ const dmarcRuaDomain = outputs.mailFromDomain || domain;
2990
3015
  dnsLines.push(
2991
3016
  "",
2992
3017
  pc2.bold("SPF Record (TXT):"),
2993
3018
  ` ${pc2.cyan(domain)} ${pc2.dim("TXT")} "v=spf1 include:amazonses.com ~all"`,
3019
+ pc2.dim(" Note: If you have an existing SPF record, add 'include:amazonses.com' to it"),
2994
3020
  "",
2995
3021
  pc2.bold("DMARC Record (TXT):"),
2996
- ` ${pc2.cyan(`_dmarc.${domain}`)} ${pc2.dim("TXT")} "v=DMARC1; p=quarantine; rua=mailto:postmaster@${domain}"`
3022
+ ` ${pc2.cyan(`_dmarc.${domain}`)} ${pc2.dim("TXT")} "v=DMARC1; p=quarantine; rua=mailto:postmaster@${dmarcRuaDomain}"`
2997
3023
  );
2998
3024
  if (outputs.mailFromDomain) {
2999
3025
  dnsLines.push(
@@ -3051,7 +3077,7 @@ Verification should complete within a few minutes.`,
3051
3077
  if (outputs.customTrackingDomain) {
3052
3078
  console.log(
3053
3079
  `
3054
- ${pc2.dim("Run:")} ${pc2.yellow(`wraps verify --domain ${outputs.customTrackingDomain}`)} ${pc2.dim(
3080
+ ${pc2.dim("Run:")} ${pc2.yellow(`wraps email verify --domain ${outputs.customTrackingDomain}`)} ${pc2.dim(
3055
3081
  "(after DNS propagates)"
3056
3082
  )}
3057
3083
  `
@@ -3103,7 +3129,7 @@ ${domainStrings.join("\n")}`);
3103
3129
  );
3104
3130
  } else {
3105
3131
  featureLines.push(
3106
- ` ${pc2.dim("\u25CB")} Email Tracking ${pc2.dim("(run 'wraps upgrade' to enable)")}`
3132
+ ` ${pc2.dim("\u25CB")} Email Tracking ${pc2.dim("(run 'wraps email upgrade' to enable)")}`
3107
3133
  );
3108
3134
  }
3109
3135
  if (status2.resources.lambdaFunctions && status2.resources.lambdaFunctions > 0) {
@@ -3112,7 +3138,7 @@ ${domainStrings.join("\n")}`);
3112
3138
  );
3113
3139
  } else {
3114
3140
  featureLines.push(
3115
- ` ${pc2.dim("\u25CB")} Bounce/Complaint Handling ${pc2.dim("(run 'wraps upgrade' to enable)")}`
3141
+ ` ${pc2.dim("\u25CB")} Bounce/Complaint Handling ${pc2.dim("(run 'wraps email upgrade' to enable)")}`
3116
3142
  );
3117
3143
  }
3118
3144
  if (status2.resources.archivingEnabled) {
@@ -3129,7 +3155,7 @@ ${domainStrings.join("\n")}`);
3129
3155
  );
3130
3156
  } else {
3131
3157
  featureLines.push(
3132
- ` ${pc2.dim("\u25CB")} Email Archiving ${pc2.dim("(run 'wraps upgrade' to enable)")}`
3158
+ ` ${pc2.dim("\u25CB")} Email Archiving ${pc2.dim("(run 'wraps email upgrade' to enable)")}`
3133
3159
  );
3134
3160
  }
3135
3161
  if (status2.tracking?.customTrackingDomain) {
@@ -3142,7 +3168,7 @@ ${domainStrings.join("\n")}`);
3142
3168
  featureLines.push(` ${pc2.cyan(status2.tracking.customTrackingDomain)}`);
3143
3169
  } else {
3144
3170
  featureLines.push(
3145
- ` ${pc2.dim("\u25CB")} Custom Tracking Domain ${pc2.dim("(run 'wraps upgrade' to enable)")}`
3171
+ ` ${pc2.dim("\u25CB")} Custom Tracking Domain ${pc2.dim("(run 'wraps email upgrade' to enable)")}`
3146
3172
  );
3147
3173
  }
3148
3174
  featureLines.push(
@@ -3190,6 +3216,7 @@ ${domainStrings.join("\n")}`);
3190
3216
  for (const domain of domainsNeedingDNS) {
3191
3217
  const dnsLines = [];
3192
3218
  if (domain.status === "pending" && domain.dkimTokens && domain.dkimTokens.length > 0) {
3219
+ const dmarcRuaDomain = domain.mailFromDomain || domain.domain;
3193
3220
  dnsLines.push(
3194
3221
  pc2.bold("DKIM Records (CNAME):"),
3195
3222
  ...domain.dkimTokens.map(
@@ -3198,9 +3225,10 @@ ${domainStrings.join("\n")}`);
3198
3225
  "",
3199
3226
  pc2.bold("SPF Record (TXT):"),
3200
3227
  ` ${pc2.cyan(domain.domain)} ${pc2.dim("TXT")} "v=spf1 include:amazonses.com ~all"`,
3228
+ pc2.dim(" Note: If you have an existing SPF record, add 'include:amazonses.com' to it"),
3201
3229
  "",
3202
3230
  pc2.bold("DMARC Record (TXT):"),
3203
- ` ${pc2.cyan(`_dmarc.${domain.domain}`)} ${pc2.dim("TXT")} "v=DMARC1; p=quarantine; rua=mailto:postmaster@${domain.domain}"`
3231
+ ` ${pc2.cyan(`_dmarc.${domain.domain}`)} ${pc2.dim("TXT")} "v=DMARC1; p=quarantine; rua=mailto:postmaster@${dmarcRuaDomain}"`
3204
3232
  );
3205
3233
  }
3206
3234
  if (domain.mailFromDomain && domain.mailFromStatus !== "SUCCESS") {
@@ -3220,7 +3248,7 @@ ${domainStrings.join("\n")}`);
3220
3248
  const exampleDomain = domainsNeedingDNS[0].domain;
3221
3249
  console.log(
3222
3250
  `
3223
- ${pc2.dim("Run:")} ${pc2.yellow(`wraps verify --domain ${exampleDomain}`)} ${pc2.dim(
3251
+ ${pc2.dim("Run:")} ${pc2.yellow(`wraps email verify --domain ${exampleDomain}`)} ${pc2.dim(
3224
3252
  "(after DNS propagates)"
3225
3253
  )}
3226
3254
  `
@@ -4166,20 +4194,22 @@ async function createSESResources(config2) {
4166
4194
  dkimTokens = domainIdentity.dkimSigningAttributes.apply(
4167
4195
  (attrs) => attrs?.tokens || []
4168
4196
  );
4169
- mailFromDomain = config2.mailFromDomain || `mail.${config2.domain}`;
4170
- new aws5.sesv2.EmailIdentityMailFromAttributes(
4171
- "wraps-email-mail-from",
4172
- {
4173
- emailIdentity: config2.domain,
4174
- mailFromDomain,
4175
- behaviorOnMxFailure: "USE_DEFAULT_VALUE"
4176
- // Fallback to amazonses.com if MX record fails
4177
- },
4178
- {
4179
- dependsOn: [domainIdentity]
4180
- // Ensure domain identity exists first
4181
- }
4182
- );
4197
+ if (config2.mailFromDomain) {
4198
+ mailFromDomain = config2.mailFromDomain;
4199
+ new aws5.sesv2.EmailIdentityMailFromAttributes(
4200
+ "wraps-email-mail-from",
4201
+ {
4202
+ emailIdentity: config2.domain,
4203
+ mailFromDomain,
4204
+ behaviorOnMxFailure: "USE_DEFAULT_VALUE"
4205
+ // Fallback to amazonses.com if MX record fails
4206
+ },
4207
+ {
4208
+ dependsOn: [domainIdentity]
4209
+ // Ensure domain identity exists first
4210
+ }
4211
+ );
4212
+ }
4183
4213
  }
4184
4214
  return {
4185
4215
  configSet,
@@ -4315,8 +4345,8 @@ async function deployEmailStack(config2) {
4315
4345
  let cloudFrontResources;
4316
4346
  let acmResources;
4317
4347
  if (emailConfig.tracking?.enabled && emailConfig.tracking.customRedirectDomain && emailConfig.tracking.httpsEnabled) {
4318
- const { findHostedZone: findHostedZone2 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
4319
- const hostedZone = await findHostedZone2(
4348
+ const { findHostedZone: findHostedZone3 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
4349
+ const hostedZone = await findHostedZone3(
4320
4350
  emailConfig.tracking.customRedirectDomain,
4321
4351
  config2.region
4322
4352
  );
@@ -4342,9 +4372,13 @@ async function deployEmailStack(config2) {
4342
4372
  "wraps-email-eventbridge",
4343
4373
  config2.region
4344
4374
  );
4375
+ let mailFromDomain = emailConfig.mailFromDomain;
4376
+ if (!mailFromDomain && emailConfig.mailFromSubdomain && emailConfig.domain) {
4377
+ mailFromDomain = `${emailConfig.mailFromSubdomain}.${emailConfig.domain}`;
4378
+ }
4345
4379
  sesResources = await createSESResources({
4346
4380
  domain: emailConfig.domain,
4347
- mailFromDomain: emailConfig.mailFromDomain,
4381
+ mailFromDomain,
4348
4382
  region: config2.region,
4349
4383
  trackingConfig: emailConfig.tracking,
4350
4384
  eventTypes: emailConfig.eventTracking?.events,
@@ -5177,8 +5211,8 @@ async function connect(options) {
5177
5211
  throw new Error(`Pulumi deployment failed: ${error.message}`);
5178
5212
  }
5179
5213
  if (outputs.domain && outputs.dkimTokens && outputs.dkimTokens.length > 0) {
5180
- const { findHostedZone: findHostedZone2, createDNSRecords: createDNSRecords2 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
5181
- const hostedZone = await findHostedZone2(outputs.domain, region);
5214
+ const { findHostedZone: findHostedZone3, createDNSRecords: createDNSRecords2 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
5215
+ const hostedZone = await findHostedZone3(outputs.domain, region);
5182
5216
  if (hostedZone) {
5183
5217
  try {
5184
5218
  progress.start("Creating DNS records in Route53");
@@ -5263,119 +5297,412 @@ ${pc6.dim("Example:")}`);
5263
5297
  });
5264
5298
  }
5265
5299
 
5266
- // src/commands/email/domains.ts
5300
+ // src/commands/email/destroy.ts
5267
5301
  init_esm_shims();
5268
5302
  init_events();
5269
5303
  init_aws();
5270
- import { Resolver } from "dns/promises";
5271
- import { GetEmailIdentityCommand, SESv2Client as SESv2Client2 } from "@aws-sdk/client-sesv2";
5304
+ init_errors();
5272
5305
  import * as clack6 from "@clack/prompts";
5306
+ import * as pulumi7 from "@pulumi/pulumi";
5273
5307
  import pc7 from "picocolors";
5274
- async function verifyDomain(options) {
5275
- clack6.intro(pc7.bold(`Verifying ${options.domain}`));
5276
- const progress = new DeploymentProgress();
5277
- const region = await getAWSRegion();
5278
- const sesClient = new SESv2Client2({ region });
5279
- let identity;
5280
- let dkimTokens = [];
5281
- let mailFromDomain;
5308
+
5309
+ // src/utils/route53.ts
5310
+ init_esm_shims();
5311
+ import {
5312
+ ChangeResourceRecordSetsCommand as ChangeResourceRecordSetsCommand2,
5313
+ ListHostedZonesByNameCommand as ListHostedZonesByNameCommand2,
5314
+ ListResourceRecordSetsCommand,
5315
+ Route53Client as Route53Client2
5316
+ } from "@aws-sdk/client-route-53";
5317
+ async function findHostedZone2(domain, region) {
5318
+ const client = new Route53Client2({ region });
5282
5319
  try {
5283
- identity = await progress.execute(
5284
- "Checking SES verification status",
5285
- async () => {
5286
- const response = await sesClient.send(
5287
- new GetEmailIdentityCommand({ EmailIdentity: options.domain })
5288
- );
5289
- return response;
5290
- }
5320
+ const response = await client.send(
5321
+ new ListHostedZonesByNameCommand2({
5322
+ DNSName: domain,
5323
+ MaxItems: 1
5324
+ })
5291
5325
  );
5292
- dkimTokens = identity.DkimAttributes?.Tokens || [];
5293
- mailFromDomain = identity.MailFromAttributes?.MailFromDomain;
5326
+ const zone = response.HostedZones?.[0];
5327
+ if (zone && zone.Name === `${domain}.` && zone.Id) {
5328
+ return {
5329
+ id: zone.Id.replace("/hostedzone/", ""),
5330
+ name: zone.Name
5331
+ };
5332
+ }
5333
+ return null;
5294
5334
  } catch (_error) {
5295
- progress.stop();
5296
- clack6.log.error(`Domain ${options.domain} not found in SES`);
5297
- console.log(
5298
- `
5299
- Run ${pc7.cyan(`wraps email init --domain ${options.domain}`)} to add this domain.
5300
- `
5301
- );
5302
- process.exit(1);
5303
- return;
5335
+ return null;
5304
5336
  }
5305
- const resolver = new Resolver();
5306
- resolver.setServers(["8.8.8.8", "1.1.1.1"]);
5307
- const dnsResults = [];
5308
- for (const token of dkimTokens) {
5309
- const dkimRecord = `${token}._domainkey.${options.domain}`;
5310
- try {
5311
- const records = await resolver.resolveCname(dkimRecord);
5312
- const expected = `${token}.dkim.amazonses.com`;
5313
- const found = records.some((r) => r === expected || r === `${expected}.`);
5314
- dnsResults.push({
5315
- name: dkimRecord,
5316
- type: "CNAME",
5317
- status: found ? "verified" : "incorrect",
5318
- records
5319
- });
5320
- } catch (_error) {
5321
- dnsResults.push({
5322
- name: dkimRecord,
5323
- type: "CNAME",
5324
- status: "missing"
5337
+ }
5338
+ async function deleteDNSRecords(hostedZoneId, domain, dkimTokens, region, customTrackingDomain, mailFromDomain) {
5339
+ const client = new Route53Client2({ region });
5340
+ const response = await client.send(
5341
+ new ListResourceRecordSetsCommand({
5342
+ HostedZoneId: hostedZoneId,
5343
+ MaxItems: 500
5344
+ })
5345
+ );
5346
+ const recordSets = response.ResourceRecordSets || [];
5347
+ const changes = [];
5348
+ const addDeletionIfExists = (name, type) => {
5349
+ const normalizedName = name.endsWith(".") ? name : `${name}.`;
5350
+ const record = recordSets.find(
5351
+ (rs) => rs.Name === normalizedName && rs.Type === type
5352
+ );
5353
+ if (record && record.ResourceRecords) {
5354
+ changes.push({
5355
+ Action: "DELETE",
5356
+ ResourceRecordSet: record
5325
5357
  });
5326
5358
  }
5359
+ };
5360
+ for (const token of dkimTokens) {
5361
+ addDeletionIfExists(`${token}._domainkey.${domain}`, "CNAME");
5327
5362
  }
5328
- try {
5329
- const records = await resolver.resolveTxt(options.domain);
5330
- const spfRecord = records.flat().find((r) => r.startsWith("v=spf1"));
5331
- const hasAmazonSES = spfRecord?.includes("include:amazonses.com");
5332
- dnsResults.push({
5333
- name: options.domain,
5334
- type: "TXT (SPF)",
5335
- status: hasAmazonSES ? "verified" : spfRecord ? "incorrect" : "missing",
5336
- records: spfRecord ? [spfRecord] : void 0
5337
- });
5338
- } catch (_error) {
5339
- dnsResults.push({
5340
- name: options.domain,
5341
- type: "TXT (SPF)",
5342
- status: "missing"
5343
- });
5363
+ addDeletionIfExists(`_dmarc.${domain}`, "TXT");
5364
+ if (customTrackingDomain) {
5365
+ addDeletionIfExists(customTrackingDomain, "CNAME");
5366
+ }
5367
+ if (mailFromDomain) {
5368
+ addDeletionIfExists(mailFromDomain, "MX");
5369
+ addDeletionIfExists(mailFromDomain, "TXT");
5370
+ }
5371
+ if (changes.length === 0) {
5372
+ return;
5344
5373
  }
5374
+ await client.send(
5375
+ new ChangeResourceRecordSetsCommand2({
5376
+ HostedZoneId: hostedZoneId,
5377
+ ChangeBatch: {
5378
+ Changes: changes
5379
+ }
5380
+ })
5381
+ );
5382
+ }
5383
+
5384
+ // src/commands/email/destroy.ts
5385
+ async function getEmailIdentityInfo(domain, region) {
5345
5386
  try {
5346
- const records = await resolver.resolveTxt(`_dmarc.${options.domain}`);
5347
- const dmarcRecord = records.flat().find((r) => r.startsWith("v=DMARC1"));
5348
- dnsResults.push({
5349
- name: `_dmarc.${options.domain}`,
5350
- type: "TXT (DMARC)",
5351
- status: dmarcRecord ? "verified" : "missing",
5352
- records: dmarcRecord ? [dmarcRecord] : void 0
5353
- });
5387
+ const { SESv2Client: SESv2Client5, GetEmailIdentityCommand: GetEmailIdentityCommand4 } = await import("@aws-sdk/client-sesv2");
5388
+ const ses = new SESv2Client5({ region });
5389
+ const response = await ses.send(
5390
+ new GetEmailIdentityCommand4({ EmailIdentity: domain })
5391
+ );
5392
+ return {
5393
+ dkimTokens: response.DkimAttributes?.Tokens || [],
5394
+ mailFromDomain: response.MailFromAttributes?.MailFromDomain
5395
+ };
5354
5396
  } catch (_error) {
5355
- dnsResults.push({
5356
- name: `_dmarc.${options.domain}`,
5357
- type: "TXT (DMARC)",
5358
- status: "missing"
5397
+ return { dkimTokens: [] };
5398
+ }
5399
+ }
5400
+ async function emailDestroy(options) {
5401
+ const startTime = Date.now();
5402
+ clack6.intro(
5403
+ pc7.bold(
5404
+ options.preview ? "Email Infrastructure Destruction Preview" : "Email Infrastructure Teardown"
5405
+ )
5406
+ );
5407
+ const progress = new DeploymentProgress();
5408
+ const identity = await progress.execute(
5409
+ "Validating AWS credentials",
5410
+ async () => validateAWSCredentials()
5411
+ );
5412
+ const region = await getAWSRegion();
5413
+ const metadata = await loadConnectionMetadata(identity.accountId, region);
5414
+ const emailService = metadata?.services?.email;
5415
+ const emailConfig = emailService?.config;
5416
+ const domain = emailConfig?.domain;
5417
+ const storedStackName = emailService?.pulumiStackName;
5418
+ if (!(options.force || options.preview)) {
5419
+ const confirmed = await clack6.confirm({
5420
+ message: pc7.red(
5421
+ "Are you sure you want to destroy all email infrastructure?"
5422
+ ),
5423
+ initialValue: false
5359
5424
  });
5425
+ if (clack6.isCancel(confirmed) || !confirmed) {
5426
+ clack6.cancel("Destruction cancelled.");
5427
+ process.exit(0);
5428
+ }
5360
5429
  }
5361
- if (mailFromDomain) {
5430
+ let shouldCleanDNS = false;
5431
+ let hostedZone = null;
5432
+ let dkimTokens = [];
5433
+ let mailFromDomain = emailConfig?.mailFromDomain;
5434
+ if (domain && !options.preview) {
5435
+ hostedZone = await findHostedZone2(domain, region);
5436
+ if (hostedZone) {
5437
+ const identityInfo = await getEmailIdentityInfo(domain, region);
5438
+ dkimTokens = identityInfo.dkimTokens;
5439
+ if (!mailFromDomain && identityInfo.mailFromDomain) {
5440
+ mailFromDomain = identityInfo.mailFromDomain;
5441
+ }
5442
+ if (!options.force) {
5443
+ const cleanDNS = await clack6.confirm({
5444
+ message: `Found Route53 hosted zone for ${pc7.cyan(domain)}. Delete DNS records (DKIM, DMARC, MAIL FROM)?`,
5445
+ initialValue: true
5446
+ });
5447
+ if (clack6.isCancel(cleanDNS)) {
5448
+ clack6.cancel("Destruction cancelled.");
5449
+ process.exit(0);
5450
+ }
5451
+ shouldCleanDNS = cleanDNS;
5452
+ } else {
5453
+ shouldCleanDNS = true;
5454
+ }
5455
+ }
5456
+ }
5457
+ if (options.preview) {
5362
5458
  try {
5363
- const mxRecords = await resolver.resolveMx(mailFromDomain);
5364
- const expectedMx = `feedback-smtp.${region}.amazonses.com`;
5365
- const hasMx = mxRecords.some(
5366
- (r) => r.exchange === expectedMx || r.exchange === `${expectedMx}.`
5459
+ const previewResult = await progress.execute(
5460
+ "Generating destruction preview",
5461
+ async () => {
5462
+ await ensurePulumiWorkDir();
5463
+ const stackName = storedStackName || `wraps-email-${identity.accountId}-${region}`;
5464
+ let stack;
5465
+ try {
5466
+ stack = await pulumi7.automation.LocalWorkspace.selectStack({
5467
+ stackName,
5468
+ workDir: getPulumiWorkDir()
5469
+ });
5470
+ } catch (_error) {
5471
+ throw new Error("No email infrastructure found to preview");
5472
+ }
5473
+ const result = await stack.preview({ diff: true });
5474
+ return result;
5475
+ }
5367
5476
  );
5368
- dnsResults.push({
5369
- name: mailFromDomain,
5370
- type: "MX",
5371
- status: hasMx ? "verified" : mxRecords.length > 0 ? "incorrect" : "missing",
5372
- records: mxRecords.map((r) => `${r.priority} ${r.exchange}`)
5373
- });
5374
- } catch (_error) {
5375
- dnsResults.push({
5376
- name: mailFromDomain,
5377
- type: "MX",
5378
- status: "missing"
5477
+ displayPreview({
5478
+ changeSummary: previewResult.changeSummary,
5479
+ costEstimate: "Monthly cost after destruction: $0.00",
5480
+ commandName: "wraps email destroy"
5481
+ });
5482
+ if (domain) {
5483
+ const previewHostedZone = await findHostedZone2(domain, region);
5484
+ if (previewHostedZone) {
5485
+ clack6.log.info(
5486
+ `DNS records in Route53 for ${pc7.cyan(domain)} will also be deleted`
5487
+ );
5488
+ }
5489
+ }
5490
+ clack6.outro(
5491
+ pc7.green("Preview complete. Run without --preview to destroy.")
5492
+ );
5493
+ trackServiceRemoved("email", {
5494
+ preview: true,
5495
+ duration_ms: Date.now() - startTime
5496
+ });
5497
+ return;
5498
+ } catch (error) {
5499
+ progress.stop();
5500
+ if (error.message.includes("No email infrastructure found")) {
5501
+ clack6.log.warn("No email infrastructure found to preview");
5502
+ process.exit(0);
5503
+ }
5504
+ trackError("PREVIEW_FAILED", "email destroy", { step: "preview" });
5505
+ throw new Error(`Preview failed: ${error.message}`);
5506
+ }
5507
+ }
5508
+ if (shouldCleanDNS && hostedZone && domain && dkimTokens.length > 0) {
5509
+ try {
5510
+ await progress.execute(
5511
+ `Deleting DNS records for ${domain}`,
5512
+ async () => {
5513
+ await deleteDNSRecords(
5514
+ hostedZone.id,
5515
+ domain,
5516
+ dkimTokens,
5517
+ region,
5518
+ emailConfig?.tracking?.customRedirectDomain,
5519
+ mailFromDomain
5520
+ );
5521
+ }
5522
+ );
5523
+ } catch (error) {
5524
+ clack6.log.warn(`Could not delete DNS records: ${error.message}`);
5525
+ clack6.log.info("You may need to delete them manually from Route53");
5526
+ }
5527
+ }
5528
+ try {
5529
+ await progress.execute(
5530
+ "Destroying email infrastructure (this may take 2-3 minutes)",
5531
+ async () => {
5532
+ await ensurePulumiWorkDir();
5533
+ const stackName = storedStackName || `wraps-email-${identity.accountId}-${region}`;
5534
+ let stack;
5535
+ try {
5536
+ stack = await pulumi7.automation.LocalWorkspace.selectStack({
5537
+ stackName,
5538
+ workDir: getPulumiWorkDir()
5539
+ });
5540
+ } catch (_error) {
5541
+ throw new Error("No email infrastructure found to destroy");
5542
+ }
5543
+ await stack.destroy({ onOutput: () => {
5544
+ } });
5545
+ await stack.workspace.removeStack(stackName);
5546
+ }
5547
+ );
5548
+ } catch (error) {
5549
+ progress.stop();
5550
+ if (error.message.includes("No email infrastructure found")) {
5551
+ clack6.log.warn("No email infrastructure found");
5552
+ await deleteConnectionMetadata(identity.accountId, region);
5553
+ process.exit(0);
5554
+ }
5555
+ if (error.message?.includes("stack is currently locked")) {
5556
+ trackError("STACK_LOCKED", "email destroy", { step: "destroy" });
5557
+ throw errors.stackLocked();
5558
+ }
5559
+ trackError("DESTROY_FAILED", "email destroy", { step: "destroy" });
5560
+ clack6.log.error("Email infrastructure destruction failed");
5561
+ throw error;
5562
+ }
5563
+ await deleteConnectionMetadata(identity.accountId, region);
5564
+ progress.stop();
5565
+ const deletedItems = ["AWS infrastructure"];
5566
+ if (shouldCleanDNS && hostedZone) {
5567
+ deletedItems.push("Route53 DNS records");
5568
+ }
5569
+ clack6.outro(pc7.green(`Email infrastructure has been removed`));
5570
+ if (domain) {
5571
+ console.log(`
5572
+ ${pc7.bold("Cleaned up:")}`);
5573
+ for (const item of deletedItems) {
5574
+ console.log(` ${pc7.green("\u2713")} ${item}`);
5575
+ }
5576
+ console.log(
5577
+ `
5578
+ ${pc7.dim("Note: SPF record was not deleted. Remove 'include:amazonses.com' manually if needed.")}`
5579
+ );
5580
+ }
5581
+ console.log(
5582
+ `
5583
+ Run ${pc7.cyan("wraps email init")} to deploy infrastructure again.
5584
+ `
5585
+ );
5586
+ trackServiceRemoved("email", {
5587
+ reason: "user_initiated",
5588
+ duration_ms: Date.now() - startTime,
5589
+ dns_cleaned: shouldCleanDNS
5590
+ });
5591
+ }
5592
+
5593
+ // src/commands/email/domains.ts
5594
+ init_esm_shims();
5595
+ init_events();
5596
+ init_aws();
5597
+ import { Resolver } from "dns/promises";
5598
+ import { GetEmailIdentityCommand, SESv2Client as SESv2Client2 } from "@aws-sdk/client-sesv2";
5599
+ import * as clack7 from "@clack/prompts";
5600
+ import pc8 from "picocolors";
5601
+ async function verifyDomain(options) {
5602
+ clack7.intro(pc8.bold(`Verifying ${options.domain}`));
5603
+ const progress = new DeploymentProgress();
5604
+ const region = await getAWSRegion();
5605
+ const sesClient = new SESv2Client2({ region });
5606
+ let identity;
5607
+ let dkimTokens = [];
5608
+ let mailFromDomain;
5609
+ try {
5610
+ identity = await progress.execute(
5611
+ "Checking SES verification status",
5612
+ async () => {
5613
+ const response = await sesClient.send(
5614
+ new GetEmailIdentityCommand({ EmailIdentity: options.domain })
5615
+ );
5616
+ return response;
5617
+ }
5618
+ );
5619
+ dkimTokens = identity.DkimAttributes?.Tokens || [];
5620
+ mailFromDomain = identity.MailFromAttributes?.MailFromDomain;
5621
+ } catch (_error) {
5622
+ progress.stop();
5623
+ clack7.log.error(`Domain ${options.domain} not found in SES`);
5624
+ console.log(
5625
+ `
5626
+ Run ${pc8.cyan(`wraps email init --domain ${options.domain}`)} to add this domain.
5627
+ `
5628
+ );
5629
+ process.exit(1);
5630
+ return;
5631
+ }
5632
+ const resolver = new Resolver();
5633
+ resolver.setServers(["8.8.8.8", "1.1.1.1"]);
5634
+ const dnsResults = [];
5635
+ for (const token of dkimTokens) {
5636
+ const dkimRecord = `${token}._domainkey.${options.domain}`;
5637
+ try {
5638
+ const records = await resolver.resolveCname(dkimRecord);
5639
+ const expected = `${token}.dkim.amazonses.com`;
5640
+ const found = records.some((r) => r === expected || r === `${expected}.`);
5641
+ dnsResults.push({
5642
+ name: dkimRecord,
5643
+ type: "CNAME",
5644
+ status: found ? "verified" : "incorrect",
5645
+ records
5646
+ });
5647
+ } catch (_error) {
5648
+ dnsResults.push({
5649
+ name: dkimRecord,
5650
+ type: "CNAME",
5651
+ status: "missing"
5652
+ });
5653
+ }
5654
+ }
5655
+ try {
5656
+ const records = await resolver.resolveTxt(options.domain);
5657
+ const spfRecord = records.flat().find((r) => r.startsWith("v=spf1"));
5658
+ const hasAmazonSES = spfRecord?.includes("include:amazonses.com");
5659
+ dnsResults.push({
5660
+ name: options.domain,
5661
+ type: "TXT (SPF)",
5662
+ status: hasAmazonSES ? "verified" : spfRecord ? "incorrect" : "missing",
5663
+ records: spfRecord ? [spfRecord] : void 0
5664
+ });
5665
+ } catch (_error) {
5666
+ dnsResults.push({
5667
+ name: options.domain,
5668
+ type: "TXT (SPF)",
5669
+ status: "missing"
5670
+ });
5671
+ }
5672
+ try {
5673
+ const records = await resolver.resolveTxt(`_dmarc.${options.domain}`);
5674
+ const dmarcRecord = records.flat().find((r) => r.startsWith("v=DMARC1"));
5675
+ dnsResults.push({
5676
+ name: `_dmarc.${options.domain}`,
5677
+ type: "TXT (DMARC)",
5678
+ status: dmarcRecord ? "verified" : "missing",
5679
+ records: dmarcRecord ? [dmarcRecord] : void 0
5680
+ });
5681
+ } catch (_error) {
5682
+ dnsResults.push({
5683
+ name: `_dmarc.${options.domain}`,
5684
+ type: "TXT (DMARC)",
5685
+ status: "missing"
5686
+ });
5687
+ }
5688
+ if (mailFromDomain) {
5689
+ try {
5690
+ const mxRecords = await resolver.resolveMx(mailFromDomain);
5691
+ const expectedMx = `feedback-smtp.${region}.amazonses.com`;
5692
+ const hasMx = mxRecords.some(
5693
+ (r) => r.exchange === expectedMx || r.exchange === `${expectedMx}.`
5694
+ );
5695
+ dnsResults.push({
5696
+ name: mailFromDomain,
5697
+ type: "MX",
5698
+ status: hasMx ? "verified" : mxRecords.length > 0 ? "incorrect" : "missing",
5699
+ records: mxRecords.map((r) => `${r.priority} ${r.exchange}`)
5700
+ });
5701
+ } catch (_error) {
5702
+ dnsResults.push({
5703
+ name: mailFromDomain,
5704
+ type: "MX",
5705
+ status: "missing"
5379
5706
  });
5380
5707
  }
5381
5708
  try {
@@ -5401,55 +5728,55 @@ Run ${pc7.cyan(`wraps email init --domain ${options.domain}`)} to add this domai
5401
5728
  const dkimStatus = identity.DkimAttributes?.Status || "PENDING";
5402
5729
  const mailFromStatus = identity.MailFromAttributes?.MailFromDomainStatus || "NOT_CONFIGURED";
5403
5730
  const statusLines = [
5404
- `${pc7.bold("Domain:")} ${options.domain}`,
5405
- `${pc7.bold("Verification Status:")} ${verificationStatus === "verified" ? pc7.green("\u2713 Verified") : pc7.yellow("\u23F1 Pending")}`,
5406
- `${pc7.bold("DKIM Status:")} ${dkimStatus === "SUCCESS" ? pc7.green("\u2713 Success") : pc7.yellow(`\u23F1 ${dkimStatus}`)}`
5731
+ `${pc8.bold("Domain:")} ${options.domain}`,
5732
+ `${pc8.bold("Verification Status:")} ${verificationStatus === "verified" ? pc8.green("\u2713 Verified") : pc8.yellow("\u23F1 Pending")}`,
5733
+ `${pc8.bold("DKIM Status:")} ${dkimStatus === "SUCCESS" ? pc8.green("\u2713 Success") : pc8.yellow(`\u23F1 ${dkimStatus}`)}`
5407
5734
  ];
5408
5735
  if (mailFromDomain) {
5409
5736
  statusLines.push(
5410
- `${pc7.bold("MAIL FROM Domain:")} ${mailFromDomain}`,
5411
- `${pc7.bold("MAIL FROM Status:")} ${mailFromStatus === "SUCCESS" ? pc7.green("\u2713 Success") : mailFromStatus === "NOT_CONFIGURED" ? pc7.yellow("\u23F1 Not Configured") : pc7.yellow(`\u23F1 ${mailFromStatus}`)}`
5737
+ `${pc8.bold("MAIL FROM Domain:")} ${mailFromDomain}`,
5738
+ `${pc8.bold("MAIL FROM Status:")} ${mailFromStatus === "SUCCESS" ? pc8.green("\u2713 Success") : mailFromStatus === "NOT_CONFIGURED" ? pc8.yellow("\u23F1 Not Configured") : pc8.yellow(`\u23F1 ${mailFromStatus}`)}`
5412
5739
  );
5413
5740
  }
5414
- clack6.note(statusLines.join("\n"), "SES Status");
5741
+ clack7.note(statusLines.join("\n"), "SES Status");
5415
5742
  const dnsLines = dnsResults.map((record) => {
5416
5743
  let statusIcon;
5417
5744
  let statusColor;
5418
5745
  if (record.status === "verified") {
5419
5746
  statusIcon = "\u2713";
5420
- statusColor = pc7.green;
5747
+ statusColor = pc8.green;
5421
5748
  } else if (record.status === "incorrect") {
5422
5749
  statusIcon = "\u2717";
5423
- statusColor = pc7.red;
5750
+ statusColor = pc8.red;
5424
5751
  } else {
5425
5752
  statusIcon = "\u2717";
5426
- statusColor = pc7.red;
5753
+ statusColor = pc8.red;
5427
5754
  }
5428
5755
  const recordInfo = record.records ? ` \u2192 ${record.records.join(", ")}` : "";
5429
5756
  return ` ${statusColor(statusIcon)} ${record.name} (${record.type}) ${statusColor(
5430
5757
  record.status
5431
5758
  )}${recordInfo}`;
5432
5759
  });
5433
- clack6.note(dnsLines.join("\n"), "DNS Records");
5760
+ clack7.note(dnsLines.join("\n"), "DNS Records");
5434
5761
  const allVerified = dnsResults.every((r) => r.status === "verified");
5435
5762
  const someIncorrect = dnsResults.some((r) => r.status === "incorrect");
5436
5763
  if (verificationStatus === "verified" && allVerified) {
5437
- clack6.outro(
5438
- pc7.green("\u2713 Domain is fully verified and ready to send emails!")
5764
+ clack7.outro(
5765
+ pc8.green("\u2713 Domain is fully verified and ready to send emails!")
5439
5766
  );
5440
5767
  trackFeature("domain_verified", { dns_auto_detected: true });
5441
5768
  } else if (someIncorrect) {
5442
- clack6.outro(
5443
- pc7.red("\u2717 Some DNS records are incorrect. Please update them.")
5769
+ clack7.outro(
5770
+ pc8.red("\u2717 Some DNS records are incorrect. Please update them.")
5444
5771
  );
5445
5772
  console.log(
5446
5773
  `
5447
- Run ${pc7.cyan("wraps email status")} to see the correct DNS records.
5774
+ Run ${pc8.cyan("wraps email status")} to see the correct DNS records.
5448
5775
  `
5449
5776
  );
5450
5777
  } else {
5451
- clack6.outro(
5452
- pc7.yellow("\u23F1 Waiting for DNS propagation and SES verification")
5778
+ clack7.outro(
5779
+ pc8.yellow("\u23F1 Waiting for DNS propagation and SES verification")
5453
5780
  );
5454
5781
  console.log("\nDNS records can take up to 48 hours to propagate.");
5455
5782
  console.log(
@@ -5463,7 +5790,7 @@ Run ${pc7.cyan("wraps email status")} to see the correct DNS records.
5463
5790
  });
5464
5791
  }
5465
5792
  async function addDomain(options) {
5466
- clack6.intro(pc7.bold(`Adding domain ${options.domain} to SES`));
5793
+ clack7.intro(pc8.bold(`Adding domain ${options.domain} to SES`));
5467
5794
  const progress = new DeploymentProgress();
5468
5795
  const region = await getAWSRegion();
5469
5796
  const sesClient = new SESv2Client2({ region });
@@ -5473,10 +5800,10 @@ async function addDomain(options) {
5473
5800
  new GetEmailIdentityCommand({ EmailIdentity: options.domain })
5474
5801
  );
5475
5802
  progress.stop();
5476
- clack6.log.warn(`Domain ${options.domain} already exists in SES`);
5803
+ clack7.log.warn(`Domain ${options.domain} already exists in SES`);
5477
5804
  console.log(
5478
5805
  `
5479
- Run ${pc7.cyan(`wraps email domains verify --domain ${options.domain}`)} to check verification status.
5806
+ Run ${pc8.cyan(`wraps email domains verify --domain ${options.domain}`)} to check verification status.
5480
5807
  `
5481
5808
  );
5482
5809
  return;
@@ -5501,22 +5828,22 @@ Run ${pc7.cyan(`wraps email domains verify --domain ${options.domain}`)} to chec
5501
5828
  );
5502
5829
  const dkimTokens = identity.DkimAttributes?.Tokens || [];
5503
5830
  progress.stop();
5504
- clack6.outro(pc7.green(`\u2713 Domain ${options.domain} added successfully!`));
5831
+ clack7.outro(pc8.green(`\u2713 Domain ${options.domain} added successfully!`));
5505
5832
  console.log(`
5506
- ${pc7.bold("Next steps:")}
5833
+ ${pc8.bold("Next steps:")}
5507
5834
  `);
5508
5835
  console.log("1. Add the following DKIM records to your DNS:\n");
5509
5836
  for (const token of dkimTokens) {
5510
- console.log(` ${pc7.cyan(`${token}._domainkey.${options.domain}`)}`);
5837
+ console.log(` ${pc8.cyan(`${token}._domainkey.${options.domain}`)}`);
5511
5838
  console.log(
5512
- ` ${pc7.dim("Type:")} CNAME ${pc7.dim("Value:")} ${token}.dkim.amazonses.com
5839
+ ` ${pc8.dim("Type:")} CNAME ${pc8.dim("Value:")} ${token}.dkim.amazonses.com
5513
5840
  `
5514
5841
  );
5515
5842
  }
5516
5843
  console.log(
5517
- `2. Verify DNS propagation: ${pc7.cyan(`wraps email domains verify --domain ${options.domain}`)}`
5844
+ `2. Verify DNS propagation: ${pc8.cyan(`wraps email domains verify --domain ${options.domain}`)}`
5518
5845
  );
5519
- console.log(`3. Check status: ${pc7.cyan("wraps email status")}
5846
+ console.log(`3. Check status: ${pc8.cyan("wraps email status")}
5520
5847
  `);
5521
5848
  trackCommand("email:domains:add", {
5522
5849
  success: true
@@ -5531,7 +5858,7 @@ ${pc7.bold("Next steps:")}
5531
5858
  }
5532
5859
  }
5533
5860
  async function listDomains() {
5534
- clack6.intro(pc7.bold("SES Email Domains"));
5861
+ clack7.intro(pc8.bold("SES Email Domains"));
5535
5862
  const progress = new DeploymentProgress();
5536
5863
  const region = await getAWSRegion();
5537
5864
  const sesClient = new SESv2Client2({ region });
@@ -5551,10 +5878,10 @@ async function listDomains() {
5551
5878
  );
5552
5879
  progress.stop();
5553
5880
  if (domains.length === 0) {
5554
- clack6.outro("No domains found in SES");
5881
+ clack7.outro("No domains found in SES");
5555
5882
  console.log(
5556
5883
  `
5557
- Run ${pc7.cyan("wraps email domains add <domain>")} to add a domain.
5884
+ Run ${pc8.cyan("wraps email domains add <domain>")} to add a domain.
5558
5885
  `
5559
5886
  );
5560
5887
  return;
@@ -5582,17 +5909,17 @@ Run ${pc7.cyan("wraps email domains add <domain>")} to add a domain.
5582
5909
  })
5583
5910
  );
5584
5911
  const domainLines = domainDetails.map((domain) => {
5585
- const statusIcon = domain.verified ? pc7.green("\u2713") : pc7.yellow("\u23F1");
5586
- const dkimIcon = domain.dkimStatus === "SUCCESS" ? pc7.green("\u2713") : pc7.yellow("\u23F1");
5587
- return ` ${statusIcon} ${pc7.bold(domain.name)} DKIM: ${dkimIcon} ${domain.dkimStatus}`;
5912
+ const statusIcon = domain.verified ? pc8.green("\u2713") : pc8.yellow("\u23F1");
5913
+ const dkimIcon = domain.dkimStatus === "SUCCESS" ? pc8.green("\u2713") : pc8.yellow("\u23F1");
5914
+ return ` ${statusIcon} ${pc8.bold(domain.name)} DKIM: ${dkimIcon} ${domain.dkimStatus}`;
5588
5915
  });
5589
- clack6.note(
5916
+ clack7.note(
5590
5917
  domainLines.join("\n"),
5591
5918
  `${domains.length} domain(s) in ${region}`
5592
5919
  );
5593
- clack6.outro(
5594
- pc7.dim(
5595
- `Run ${pc7.cyan("wraps email domains verify --domain <domain>")} for details`
5920
+ clack7.outro(
5921
+ pc8.dim(
5922
+ `Run ${pc8.cyan("wraps email domains verify --domain <domain>")} for details`
5596
5923
  )
5597
5924
  );
5598
5925
  trackCommand("email:domains:list", {
@@ -5606,7 +5933,7 @@ Run ${pc7.cyan("wraps email domains add <domain>")} to add a domain.
5606
5933
  }
5607
5934
  }
5608
5935
  async function getDkim(options) {
5609
- clack6.intro(pc7.bold(`DKIM Tokens for ${options.domain}`));
5936
+ clack7.intro(pc8.bold(`DKIM Tokens for ${options.domain}`));
5610
5937
  const progress = new DeploymentProgress();
5611
5938
  const region = await getAWSRegion();
5612
5939
  const sesClient = new SESv2Client2({ region });
@@ -5624,23 +5951,23 @@ async function getDkim(options) {
5624
5951
  const dkimStatus = identity.DkimAttributes?.Status || "PENDING";
5625
5952
  progress.stop();
5626
5953
  if (dkimTokens.length === 0) {
5627
- clack6.outro(pc7.yellow("No DKIM tokens found for this domain"));
5954
+ clack7.outro(pc8.yellow("No DKIM tokens found for this domain"));
5628
5955
  return;
5629
5956
  }
5630
- const statusLine = `${pc7.bold("DKIM Status:")} ${dkimStatus === "SUCCESS" ? pc7.green("\u2713 Verified") : pc7.yellow(`\u23F1 ${dkimStatus}`)}`;
5631
- clack6.note(statusLine, "Status");
5957
+ const statusLine = `${pc8.bold("DKIM Status:")} ${dkimStatus === "SUCCESS" ? pc8.green("\u2713 Verified") : pc8.yellow(`\u23F1 ${dkimStatus}`)}`;
5958
+ clack7.note(statusLine, "Status");
5632
5959
  console.log(`
5633
- ${pc7.bold("DNS Records to add:")}
5960
+ ${pc8.bold("DNS Records to add:")}
5634
5961
  `);
5635
5962
  for (const token of dkimTokens) {
5636
- console.log(`${pc7.cyan(`${token}._domainkey.${options.domain}`)}`);
5637
- console.log(` ${pc7.dim("Type:")} CNAME`);
5638
- console.log(` ${pc7.dim("Value:")} ${token}.dkim.amazonses.com
5963
+ console.log(`${pc8.cyan(`${token}._domainkey.${options.domain}`)}`);
5964
+ console.log(` ${pc8.dim("Type:")} CNAME`);
5965
+ console.log(` ${pc8.dim("Value:")} ${token}.dkim.amazonses.com
5639
5966
  `);
5640
5967
  }
5641
5968
  if (dkimStatus !== "SUCCESS") {
5642
5969
  console.log(
5643
- `${pc7.dim("After adding these records, run:")} ${pc7.cyan(`wraps email domains verify --domain ${options.domain}`)}
5970
+ `${pc8.dim("After adding these records, run:")} ${pc8.cyan(`wraps email domains verify --domain ${options.domain}`)}
5644
5971
  `
5645
5972
  );
5646
5973
  }
@@ -5652,10 +5979,10 @@ ${pc7.bold("DNS Records to add:")}
5652
5979
  progress.stop();
5653
5980
  trackCommand("email:domains:get-dkim", { success: false });
5654
5981
  if (error.name === "NotFoundException") {
5655
- clack6.log.error(`Domain ${options.domain} not found in SES`);
5982
+ clack7.log.error(`Domain ${options.domain} not found in SES`);
5656
5983
  console.log(
5657
5984
  `
5658
- Run ${pc7.cyan(`wraps email domains add ${options.domain}`)} to add this domain.
5985
+ Run ${pc8.cyan(`wraps email domains add ${options.domain}`)} to add this domain.
5659
5986
  `
5660
5987
  );
5661
5988
  process.exit(1);
@@ -5665,7 +5992,7 @@ Run ${pc7.cyan(`wraps email domains add ${options.domain}`)} to add this domain.
5665
5992
  }
5666
5993
  }
5667
5994
  async function removeDomain(options) {
5668
- clack6.intro(pc7.bold(`Remove domain ${options.domain} from SES`));
5995
+ clack7.intro(pc8.bold(`Remove domain ${options.domain} from SES`));
5669
5996
  const progress = new DeploymentProgress();
5670
5997
  const region = await getAWSRegion();
5671
5998
  const sesClient = new SESv2Client2({ region });
@@ -5677,12 +6004,12 @@ async function removeDomain(options) {
5677
6004
  });
5678
6005
  progress.stop();
5679
6006
  if (!options.force) {
5680
- const shouldContinue = await clack6.confirm({
5681
- message: `Are you sure you want to remove ${pc7.red(options.domain)} from SES?`,
6007
+ const shouldContinue = await clack7.confirm({
6008
+ message: `Are you sure you want to remove ${pc8.red(options.domain)} from SES?`,
5682
6009
  initialValue: false
5683
6010
  });
5684
- if (clack6.isCancel(shouldContinue) || !shouldContinue) {
5685
- clack6.cancel("Operation cancelled");
6011
+ if (clack7.isCancel(shouldContinue) || !shouldContinue) {
6012
+ clack7.cancel("Operation cancelled");
5686
6013
  process.exit(0);
5687
6014
  }
5688
6015
  }
@@ -5695,7 +6022,7 @@ async function removeDomain(options) {
5695
6022
  );
5696
6023
  });
5697
6024
  progress.stop();
5698
- clack6.outro(pc7.green(`\u2713 Domain ${options.domain} removed successfully`));
6025
+ clack7.outro(pc8.green(`\u2713 Domain ${options.domain} removed successfully`));
5699
6026
  trackCommand("email:domains:remove", {
5700
6027
  success: true
5701
6028
  });
@@ -5704,7 +6031,7 @@ async function removeDomain(options) {
5704
6031
  progress.stop();
5705
6032
  trackCommand("email:domains:remove", { success: false });
5706
6033
  if (error.name === "NotFoundException") {
5707
- clack6.log.error(`Domain ${options.domain} not found in SES`);
6034
+ clack7.log.error(`Domain ${options.domain} not found in SES`);
5708
6035
  process.exit(1);
5709
6036
  return;
5710
6037
  }
@@ -5714,9 +6041,9 @@ async function removeDomain(options) {
5714
6041
 
5715
6042
  // src/commands/email/init.ts
5716
6043
  init_esm_shims();
5717
- import * as clack7 from "@clack/prompts";
5718
- import * as pulumi7 from "@pulumi/pulumi";
5719
- import pc8 from "picocolors";
6044
+ import * as clack8 from "@clack/prompts";
6045
+ import * as pulumi8 from "@pulumi/pulumi";
6046
+ import pc9 from "picocolors";
5720
6047
  init_events();
5721
6048
  init_costs();
5722
6049
  init_presets();
@@ -5725,8 +6052,8 @@ init_errors();
5725
6052
  init_prompts();
5726
6053
  async function init(options) {
5727
6054
  const startTime = Date.now();
5728
- clack7.intro(
5729
- pc8.bold(
6055
+ clack8.intro(
6056
+ pc9.bold(
5730
6057
  options.preview ? "Wraps Email Infrastructure Preview" : "Wraps Email Infrastructure Setup"
5731
6058
  )
5732
6059
  );
@@ -5742,7 +6069,7 @@ async function init(options) {
5742
6069
  "Validating AWS credentials",
5743
6070
  async () => validateAWSCredentials()
5744
6071
  );
5745
- progress.info(`Connected to AWS account: ${pc8.cyan(identity.accountId)}`);
6072
+ progress.info(`Connected to AWS account: ${pc9.cyan(identity.accountId)}`);
5746
6073
  let provider = options.provider;
5747
6074
  if (!provider) {
5748
6075
  provider = await promptProvider();
@@ -5765,12 +6092,12 @@ async function init(options) {
5765
6092
  region
5766
6093
  );
5767
6094
  if (existingConnection) {
5768
- clack7.log.warn(
5769
- `Connection already exists for account ${pc8.cyan(identity.accountId)} in region ${pc8.cyan(region)}`
6095
+ clack8.log.warn(
6096
+ `Connection already exists for account ${pc9.cyan(identity.accountId)} in region ${pc9.cyan(region)}`
5770
6097
  );
5771
- clack7.log.info(`Created: ${existingConnection.timestamp}`);
5772
- clack7.log.info(`Use ${pc8.cyan("wraps status")} to view current setup`);
5773
- clack7.log.info(`Use ${pc8.cyan("wraps upgrade")} to add more features`);
6098
+ clack8.log.info(`Created: ${existingConnection.timestamp}`);
6099
+ clack8.log.info(`Use ${pc9.cyan("wraps status")} to view current setup`);
6100
+ clack8.log.info(`Use ${pc9.cyan("wraps upgrade")} to add more features`);
5774
6101
  process.exit(0);
5775
6102
  }
5776
6103
  let preset = options.preset;
@@ -5791,15 +6118,15 @@ async function init(options) {
5791
6118
  }
5792
6119
  const estimatedVolume = await promptEstimatedVolume();
5793
6120
  progress.info(`
5794
- ${pc8.bold("Cost Estimate:")}`);
6121
+ ${pc9.bold("Cost Estimate:")}`);
5795
6122
  const costSummary = getCostSummary(emailConfig, estimatedVolume);
5796
- clack7.log.info(costSummary);
6123
+ clack8.log.info(costSummary);
5797
6124
  const warnings = validateConfig(emailConfig);
5798
6125
  if (warnings.length > 0) {
5799
6126
  progress.info(`
5800
- ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
6127
+ ${pc9.yellow(pc9.bold("Configuration Warnings:"))}`);
5801
6128
  for (const warning of warnings) {
5802
- clack7.log.warn(warning);
6129
+ clack8.log.warn(warning);
5803
6130
  }
5804
6131
  }
5805
6132
  const metadata = createConnectionMetadata(
@@ -5815,7 +6142,7 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
5815
6142
  if (!(options.yes || options.preview)) {
5816
6143
  const confirmed = await confirmDeploy();
5817
6144
  if (!confirmed) {
5818
- clack7.cancel("Deployment cancelled.");
6145
+ clack8.cancel("Deployment cancelled.");
5819
6146
  process.exit(0);
5820
6147
  }
5821
6148
  }
@@ -5831,7 +6158,7 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
5831
6158
  "Generating infrastructure preview",
5832
6159
  async () => {
5833
6160
  await ensurePulumiWorkDir();
5834
- const stack = await pulumi7.automation.LocalWorkspace.createOrSelectStack(
6161
+ const stack = await pulumi8.automation.LocalWorkspace.createOrSelectStack(
5835
6162
  {
5836
6163
  stackName: `wraps-${identity.accountId}-${region}`,
5837
6164
  projectName: "wraps-email",
@@ -5872,8 +6199,8 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
5872
6199
  costEstimate: costSummary,
5873
6200
  commandName: "wraps email init"
5874
6201
  });
5875
- clack7.outro(
5876
- pc8.green("Preview complete. Run without --preview to deploy.")
6202
+ clack8.outro(
6203
+ pc9.green("Preview complete. Run without --preview to deploy.")
5877
6204
  );
5878
6205
  trackServiceInit("email", true, {
5879
6206
  preset,
@@ -5896,7 +6223,7 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
5896
6223
  "Deploying infrastructure (this may take 2-3 minutes)",
5897
6224
  async () => {
5898
6225
  await ensurePulumiWorkDir();
5899
- const stack = await pulumi7.automation.LocalWorkspace.createOrSelectStack(
6226
+ const stack = await pulumi8.automation.LocalWorkspace.createOrSelectStack(
5900
6227
  {
5901
6228
  stackName: `wraps-${identity.accountId}-${region}`,
5902
6229
  projectName: "wraps-email",
@@ -5967,13 +6294,19 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
5967
6294
  }
5968
6295
  if (metadata.services.email) {
5969
6296
  metadata.services.email.pulumiStackName = `wraps-${identity.accountId}-${region}`;
6297
+ if (outputs.mailFromDomain) {
6298
+ metadata.services.email.config.mailFromDomain = outputs.mailFromDomain;
6299
+ }
6300
+ if (outputs.customTrackingDomain && metadata.services.email.config.tracking) {
6301
+ metadata.services.email.config.tracking.customRedirectDomain = outputs.customTrackingDomain;
6302
+ }
5970
6303
  }
5971
6304
  await saveConnectionMetadata(metadata);
5972
6305
  progress.info("Connection metadata saved for upgrade and restore capability");
5973
6306
  let dnsAutoCreated = false;
5974
6307
  if (outputs.domain && outputs.dkimTokens && outputs.dkimTokens.length > 0) {
5975
- const { findHostedZone: findHostedZone2, createDNSRecords: createDNSRecords2 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
5976
- const hostedZone = await findHostedZone2(outputs.domain, region);
6308
+ const { findHostedZone: findHostedZone3, createDNSRecords: createDNSRecords2 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
6309
+ const hostedZone = await findHostedZone3(outputs.domain, region);
5977
6310
  if (hostedZone) {
5978
6311
  try {
5979
6312
  progress.start("Creating DNS records in Route53");
@@ -5989,7 +6322,7 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
5989
6322
  dnsAutoCreated = true;
5990
6323
  } catch (error) {
5991
6324
  progress.fail("Failed to create DNS records in Route53");
5992
- clack7.log.warn(`Could not auto-create DNS records: ${error.message}`);
6325
+ clack8.log.warn(`Could not auto-create DNS records: ${error.message}`);
5993
6326
  }
5994
6327
  }
5995
6328
  }
@@ -6042,20 +6375,20 @@ ${pc8.yellow(pc8.bold("Configuration Warnings:"))}`);
6042
6375
  init_esm_shims();
6043
6376
  init_events();
6044
6377
  init_aws();
6045
- import * as clack8 from "@clack/prompts";
6046
- import * as pulumi8 from "@pulumi/pulumi";
6047
- import pc9 from "picocolors";
6378
+ import * as clack9 from "@clack/prompts";
6379
+ import * as pulumi9 from "@pulumi/pulumi";
6380
+ import pc10 from "picocolors";
6048
6381
  async function restore(options) {
6049
6382
  const startTime = Date.now();
6050
- clack8.intro(
6051
- pc9.bold(
6383
+ clack9.intro(
6384
+ pc10.bold(
6052
6385
  options.preview ? "Wraps Restore Preview" : "Wraps Restore - Remove Wraps Infrastructure"
6053
6386
  )
6054
6387
  );
6055
- clack8.log.info(
6056
- `${pc9.yellow("Note:")} This will remove all Wraps-managed infrastructure.`
6388
+ clack9.log.info(
6389
+ `${pc10.yellow("Note:")} This will remove all Wraps-managed infrastructure.`
6057
6390
  );
6058
- clack8.log.info(
6391
+ clack9.log.info(
6059
6392
  "Your original AWS resources remain untouched (Wraps never modifies them).\n"
6060
6393
  );
6061
6394
  const progress = new DeploymentProgress();
@@ -6063,7 +6396,7 @@ async function restore(options) {
6063
6396
  "Validating AWS credentials",
6064
6397
  async () => validateAWSCredentials()
6065
6398
  );
6066
- progress.info(`Connected to AWS account: ${pc9.cyan(identity.accountId)}`);
6399
+ progress.info(`Connected to AWS account: ${pc10.cyan(identity.accountId)}`);
6067
6400
  let region = options.region;
6068
6401
  if (!region) {
6069
6402
  const defaultRegion = await getAWSRegion();
@@ -6071,40 +6404,40 @@ async function restore(options) {
6071
6404
  }
6072
6405
  const metadata = await loadConnectionMetadata(identity.accountId, region);
6073
6406
  if (!metadata) {
6074
- clack8.log.error(
6075
- `No Wraps connection found for account ${pc9.cyan(identity.accountId)} in region ${pc9.cyan(region)}`
6407
+ clack9.log.error(
6408
+ `No Wraps connection found for account ${pc10.cyan(identity.accountId)} in region ${pc10.cyan(region)}`
6076
6409
  );
6077
- clack8.log.info(
6078
- `Use ${pc9.cyan("wraps email init")} or ${pc9.cyan("wraps email connect")} to create a connection first.`
6410
+ clack9.log.info(
6411
+ `Use ${pc10.cyan("wraps email init")} or ${pc10.cyan("wraps email connect")} to create a connection first.`
6079
6412
  );
6080
6413
  process.exit(1);
6081
6414
  }
6082
6415
  progress.info(`Found connection created: ${metadata.timestamp}`);
6083
6416
  console.log(
6084
6417
  `
6085
- ${pc9.bold("The following Wraps resources will be removed:")}
6418
+ ${pc10.bold("The following Wraps resources will be removed:")}
6086
6419
  `
6087
6420
  );
6088
6421
  if (metadata.services.email?.config.tracking?.enabled) {
6089
- console.log(` ${pc9.cyan("\u2713")} Configuration Set (wraps-email-tracking)`);
6422
+ console.log(` ${pc10.cyan("\u2713")} Configuration Set (wraps-email-tracking)`);
6090
6423
  }
6091
6424
  if (metadata.services.email?.config.eventTracking?.dynamoDBHistory) {
6092
- console.log(` ${pc9.cyan("\u2713")} DynamoDB Table (wraps-email-history)`);
6425
+ console.log(` ${pc10.cyan("\u2713")} DynamoDB Table (wraps-email-history)`);
6093
6426
  }
6094
6427
  if (metadata.services.email?.config.eventTracking?.enabled) {
6095
- console.log(` ${pc9.cyan("\u2713")} EventBridge Rules`);
6096
- console.log(` ${pc9.cyan("\u2713")} SQS Queues`);
6097
- console.log(` ${pc9.cyan("\u2713")} Lambda Functions`);
6428
+ console.log(` ${pc10.cyan("\u2713")} EventBridge Rules`);
6429
+ console.log(` ${pc10.cyan("\u2713")} SQS Queues`);
6430
+ console.log(` ${pc10.cyan("\u2713")} Lambda Functions`);
6098
6431
  }
6099
- console.log(` ${pc9.cyan("\u2713")} IAM Role (wraps-email-role)`);
6432
+ console.log(` ${pc10.cyan("\u2713")} IAM Role (wraps-email-role)`);
6100
6433
  console.log("");
6101
6434
  if (!(options.force || options.preview)) {
6102
- const confirmed = await clack8.confirm({
6435
+ const confirmed = await clack9.confirm({
6103
6436
  message: "Proceed with removal? This cannot be undone.",
6104
6437
  initialValue: false
6105
6438
  });
6106
- if (clack8.isCancel(confirmed) || !confirmed) {
6107
- clack8.cancel("Removal cancelled.");
6439
+ if (clack9.isCancel(confirmed) || !confirmed) {
6440
+ clack9.cancel("Removal cancelled.");
6108
6441
  process.exit(0);
6109
6442
  }
6110
6443
  }
@@ -6114,7 +6447,7 @@ ${pc9.bold("The following Wraps resources will be removed:")}
6114
6447
  const previewResult = await progress.execute(
6115
6448
  "Generating removal preview",
6116
6449
  async () => {
6117
- const stack = await pulumi8.automation.LocalWorkspace.selectStack(
6450
+ const stack = await pulumi9.automation.LocalWorkspace.selectStack(
6118
6451
  {
6119
6452
  stackName: metadata.services.email.pulumiStackName,
6120
6453
  projectName: "wraps-email",
@@ -6140,8 +6473,8 @@ ${pc9.bold("The following Wraps resources will be removed:")}
6140
6473
  costEstimate: "Monthly cost after removal: $0.00",
6141
6474
  commandName: "wraps email restore"
6142
6475
  });
6143
- clack8.outro(
6144
- pc9.green(
6476
+ clack9.outro(
6477
+ pc10.green(
6145
6478
  "Preview complete. Run without --preview to remove infrastructure."
6146
6479
  )
6147
6480
  );
@@ -6163,7 +6496,7 @@ ${pc9.bold("The following Wraps resources will be removed:")}
6163
6496
  if (!metadata.services.email?.pulumiStackName) {
6164
6497
  throw new Error("No Pulumi stack name found in metadata");
6165
6498
  }
6166
- const stack = await pulumi8.automation.LocalWorkspace.selectStack(
6499
+ const stack = await pulumi9.automation.LocalWorkspace.selectStack(
6167
6500
  {
6168
6501
  stackName: metadata.services.email.pulumiStackName,
6169
6502
  projectName: "wraps-email",
@@ -6195,13 +6528,13 @@ ${pc9.bold("The following Wraps resources will be removed:")}
6195
6528
  progress.info("Connection metadata deleted");
6196
6529
  console.log(
6197
6530
  `
6198
- ${pc9.green("\u2713")} ${pc9.bold("Infrastructure removed successfully!")}
6531
+ ${pc10.green("\u2713")} ${pc10.bold("Infrastructure removed successfully!")}
6199
6532
  `
6200
6533
  );
6201
6534
  console.log(
6202
- `${pc9.dim("All Wraps resources have been deleted from your AWS account.")}`
6535
+ `${pc10.dim("All Wraps resources have been deleted from your AWS account.")}`
6203
6536
  );
6204
- console.log(`${pc9.dim("Your original AWS resources remain unchanged.")}
6537
+ console.log(`${pc10.dim("Your original AWS resources remain unchanged.")}
6205
6538
  `);
6206
6539
  trackServiceRemoved("email", {
6207
6540
  reason: "user_initiated",
@@ -6209,11 +6542,102 @@ ${pc9.green("\u2713")} ${pc9.bold("Infrastructure removed successfully!")}
6209
6542
  });
6210
6543
  }
6211
6544
 
6545
+ // src/commands/email/status.ts
6546
+ init_esm_shims();
6547
+ init_events();
6548
+ init_aws();
6549
+ import * as clack10 from "@clack/prompts";
6550
+ import * as pulumi10 from "@pulumi/pulumi";
6551
+ import pc11 from "picocolors";
6552
+ async function emailStatus(_options) {
6553
+ const startTime = Date.now();
6554
+ const progress = new DeploymentProgress();
6555
+ clack10.intro(pc11.bold("Wraps Email Status"));
6556
+ const identity = await progress.execute(
6557
+ "Loading email infrastructure status",
6558
+ async () => validateAWSCredentials()
6559
+ );
6560
+ const region = await getAWSRegion();
6561
+ let stackOutputs = {};
6562
+ try {
6563
+ await ensurePulumiWorkDir();
6564
+ const stack = await pulumi10.automation.LocalWorkspace.selectStack({
6565
+ stackName: `wraps-${identity.accountId}-${region}`,
6566
+ workDir: getPulumiWorkDir()
6567
+ });
6568
+ stackOutputs = await stack.outputs();
6569
+ } catch (_error) {
6570
+ progress.stop();
6571
+ clack10.log.error("No email infrastructure found");
6572
+ console.log(
6573
+ `
6574
+ Run ${pc11.cyan("wraps email init")} to deploy email infrastructure.
6575
+ `
6576
+ );
6577
+ process.exit(1);
6578
+ }
6579
+ const domains = await listSESDomains(region);
6580
+ const { SESv2Client: SESv2Client5, GetEmailIdentityCommand: GetEmailIdentityCommand4 } = await import("@aws-sdk/client-sesv2");
6581
+ const sesv2Client = new SESv2Client5({ region });
6582
+ const domainsWithTokens = await Promise.all(
6583
+ domains.map(async (d) => {
6584
+ try {
6585
+ const identity2 = await sesv2Client.send(
6586
+ new GetEmailIdentityCommand4({ EmailIdentity: d.domain })
6587
+ );
6588
+ return {
6589
+ domain: d.domain,
6590
+ status: d.verified ? "verified" : "pending",
6591
+ dkimTokens: identity2.DkimAttributes?.Tokens || [],
6592
+ mailFromDomain: identity2.MailFromAttributes?.MailFromDomain,
6593
+ mailFromStatus: identity2.MailFromAttributes?.MailFromDomainStatus
6594
+ };
6595
+ } catch (_error) {
6596
+ return {
6597
+ domain: d.domain,
6598
+ status: d.verified ? "verified" : "pending",
6599
+ dkimTokens: void 0,
6600
+ mailFromDomain: void 0,
6601
+ mailFromStatus: void 0
6602
+ };
6603
+ }
6604
+ })
6605
+ );
6606
+ const integrationLevel = stackOutputs.configSetName ? "enhanced" : "dashboard-only";
6607
+ progress.stop();
6608
+ displayStatus({
6609
+ integrationLevel,
6610
+ region,
6611
+ domains: domainsWithTokens,
6612
+ resources: {
6613
+ roleArn: stackOutputs.roleArn?.value,
6614
+ configSetName: stackOutputs.configSetName?.value,
6615
+ tableName: stackOutputs.tableName?.value,
6616
+ lambdaFunctions: stackOutputs.lambdaFunctions?.value?.length || 0,
6617
+ snsTopics: integrationLevel === "enhanced" ? 1 : 0,
6618
+ archiveArn: stackOutputs.archiveArn?.value,
6619
+ archivingEnabled: stackOutputs.archivingEnabled?.value,
6620
+ archiveRetention: stackOutputs.archiveRetention?.value
6621
+ },
6622
+ tracking: stackOutputs.customTrackingDomain?.value ? {
6623
+ customTrackingDomain: stackOutputs.customTrackingDomain?.value,
6624
+ httpsEnabled: stackOutputs.httpsTrackingEnabled?.value,
6625
+ cloudFrontDomain: stackOutputs.cloudFrontDomain?.value
6626
+ } : void 0
6627
+ });
6628
+ trackCommand("email:status", {
6629
+ success: true,
6630
+ domain_count: domainsWithTokens.length,
6631
+ integration_level: integrationLevel,
6632
+ duration_ms: Date.now() - startTime
6633
+ });
6634
+ }
6635
+
6212
6636
  // src/commands/email/upgrade.ts
6213
6637
  init_esm_shims();
6214
- import * as clack9 from "@clack/prompts";
6215
- import * as pulumi9 from "@pulumi/pulumi";
6216
- import pc10 from "picocolors";
6638
+ import * as clack11 from "@clack/prompts";
6639
+ import * as pulumi11 from "@pulumi/pulumi";
6640
+ import pc12 from "picocolors";
6217
6641
  init_events();
6218
6642
  init_costs();
6219
6643
  init_presets();
@@ -6223,8 +6647,8 @@ init_prompts();
6223
6647
  async function upgrade(options) {
6224
6648
  const startTime = Date.now();
6225
6649
  let upgradeAction = "";
6226
- clack9.intro(
6227
- pc10.bold(
6650
+ clack11.intro(
6651
+ pc12.bold(
6228
6652
  options.preview ? "Wraps Upgrade Preview" : "Wraps Upgrade - Enhance Your Email Infrastructure"
6229
6653
  )
6230
6654
  );
@@ -6240,7 +6664,7 @@ async function upgrade(options) {
6240
6664
  "Validating AWS credentials",
6241
6665
  async () => validateAWSCredentials()
6242
6666
  );
6243
- progress.info(`Connected to AWS account: ${pc10.cyan(identity.accountId)}`);
6667
+ progress.info(`Connected to AWS account: ${pc12.cyan(identity.accountId)}`);
6244
6668
  let region = options.region;
6245
6669
  if (!region) {
6246
6670
  const defaultRegion = await getAWSRegion();
@@ -6248,55 +6672,55 @@ async function upgrade(options) {
6248
6672
  }
6249
6673
  const metadata = await loadConnectionMetadata(identity.accountId, region);
6250
6674
  if (!metadata) {
6251
- clack9.log.error(
6252
- `No Wraps connection found for account ${pc10.cyan(identity.accountId)} in region ${pc10.cyan(region)}`
6675
+ clack11.log.error(
6676
+ `No Wraps connection found for account ${pc12.cyan(identity.accountId)} in region ${pc12.cyan(region)}`
6253
6677
  );
6254
- clack9.log.info(
6255
- `Use ${pc10.cyan("wraps email init")} to create new infrastructure or ${pc10.cyan("wraps email connect")} to connect existing.`
6678
+ clack11.log.info(
6679
+ `Use ${pc12.cyan("wraps email init")} to create new infrastructure or ${pc12.cyan("wraps email connect")} to connect existing.`
6256
6680
  );
6257
6681
  process.exit(1);
6258
6682
  }
6259
6683
  progress.info(`Found existing connection created: ${metadata.timestamp}`);
6260
6684
  console.log(`
6261
- ${pc10.bold("Current Configuration:")}
6685
+ ${pc12.bold("Current Configuration:")}
6262
6686
  `);
6263
6687
  if (metadata.services.email?.preset) {
6264
- console.log(` Preset: ${pc10.cyan(metadata.services.email?.preset)}`);
6688
+ console.log(` Preset: ${pc12.cyan(metadata.services.email?.preset)}`);
6265
6689
  } else {
6266
- console.log(` Preset: ${pc10.cyan("custom")}`);
6690
+ console.log(` Preset: ${pc12.cyan("custom")}`);
6267
6691
  }
6268
6692
  const config2 = metadata.services.email?.config;
6269
6693
  if (!config2) {
6270
- clack9.log.error("No email configuration found in metadata");
6271
- clack9.log.info(
6272
- `Use ${pc10.cyan("wraps email init")} to create new infrastructure.`
6694
+ clack11.log.error("No email configuration found in metadata");
6695
+ clack11.log.info(
6696
+ `Use ${pc12.cyan("wraps email init")} to create new infrastructure.`
6273
6697
  );
6274
6698
  process.exit(1);
6275
6699
  }
6276
6700
  if (config2.domain) {
6277
- console.log(` Sending Domain: ${pc10.cyan(config2.domain)}`);
6701
+ console.log(` Sending Domain: ${pc12.cyan(config2.domain)}`);
6278
6702
  }
6279
6703
  if (config2.tracking?.enabled) {
6280
- console.log(` ${pc10.green("\u2713")} Open & Click Tracking`);
6704
+ console.log(` ${pc12.green("\u2713")} Open & Click Tracking`);
6281
6705
  if (config2.tracking.customRedirectDomain) {
6282
6706
  console.log(
6283
- ` ${pc10.dim("\u2514\u2500")} Custom domain: ${pc10.cyan(config2.tracking.customRedirectDomain)}`
6707
+ ` ${pc12.dim("\u2514\u2500")} Custom domain: ${pc12.cyan(config2.tracking.customRedirectDomain)}`
6284
6708
  );
6285
6709
  }
6286
6710
  }
6287
6711
  if (config2.suppressionList?.enabled) {
6288
- console.log(` ${pc10.green("\u2713")} Bounce/Complaint Suppression`);
6712
+ console.log(` ${pc12.green("\u2713")} Bounce/Complaint Suppression`);
6289
6713
  }
6290
6714
  if (config2.eventTracking?.enabled) {
6291
- console.log(` ${pc10.green("\u2713")} Event Tracking (EventBridge)`);
6715
+ console.log(` ${pc12.green("\u2713")} Event Tracking (EventBridge)`);
6292
6716
  if (config2.eventTracking.dynamoDBHistory) {
6293
6717
  console.log(
6294
- ` ${pc10.dim("\u2514\u2500")} Email History: ${pc10.cyan(config2.eventTracking.archiveRetention || "90days")}`
6718
+ ` ${pc12.dim("\u2514\u2500")} Email History: ${pc12.cyan(config2.eventTracking.archiveRetention || "90days")}`
6295
6719
  );
6296
6720
  }
6297
6721
  }
6298
6722
  if (config2.dedicatedIp) {
6299
- console.log(` ${pc10.green("\u2713")} Dedicated IP Address`);
6723
+ console.log(` ${pc12.green("\u2713")} Dedicated IP Address`);
6300
6724
  }
6301
6725
  if (config2.emailArchiving?.enabled) {
6302
6726
  const retentionLabel = {
@@ -6321,15 +6745,15 @@ ${pc10.bold("Current Configuration:")}
6321
6745
  indefinite: "indefinite",
6322
6746
  permanent: "permanent"
6323
6747
  }[config2.emailArchiving.retention] || "90 days";
6324
- console.log(` ${pc10.green("\u2713")} Email Archiving (${retentionLabel})`);
6748
+ console.log(` ${pc12.green("\u2713")} Email Archiving (${retentionLabel})`);
6325
6749
  }
6326
6750
  const currentCostData = calculateCosts(config2, 5e4);
6327
6751
  console.log(
6328
6752
  `
6329
- Estimated Cost: ${pc10.cyan(`~${formatCost(currentCostData.total.monthly)}/mo`)}`
6753
+ Estimated Cost: ${pc12.cyan(`~${formatCost(currentCostData.total.monthly)}/mo`)}`
6330
6754
  );
6331
6755
  console.log("");
6332
- upgradeAction = await clack9.select({
6756
+ upgradeAction = await clack11.select({
6333
6757
  message: "What would you like to do?",
6334
6758
  options: [
6335
6759
  {
@@ -6369,8 +6793,8 @@ ${pc10.bold("Current Configuration:")}
6369
6793
  }
6370
6794
  ]
6371
6795
  });
6372
- if (clack9.isCancel(upgradeAction)) {
6373
- clack9.cancel("Upgrade cancelled.");
6796
+ if (clack11.isCancel(upgradeAction)) {
6797
+ clack11.cancel("Upgrade cancelled.");
6374
6798
  process.exit(0);
6375
6799
  }
6376
6800
  let updatedConfig = { ...config2 };
@@ -6388,15 +6812,15 @@ ${pc10.bold("Current Configuration:")}
6388
6812
  disabled: currentPresetIdx >= 0 && idx <= currentPresetIdx ? "Current or lower tier" : void 0
6389
6813
  })).filter((p) => !p.disabled);
6390
6814
  if (availablePresets.length === 0) {
6391
- clack9.log.warn("Already on highest preset (Enterprise)");
6815
+ clack11.log.warn("Already on highest preset (Enterprise)");
6392
6816
  process.exit(0);
6393
6817
  }
6394
- const selectedPreset = await clack9.select({
6818
+ const selectedPreset = await clack11.select({
6395
6819
  message: "Select new preset:",
6396
6820
  options: availablePresets
6397
6821
  });
6398
- if (clack9.isCancel(selectedPreset)) {
6399
- clack9.cancel("Upgrade cancelled.");
6822
+ if (clack11.isCancel(selectedPreset)) {
6823
+ clack11.cancel("Upgrade cancelled.");
6400
6824
  process.exit(0);
6401
6825
  }
6402
6826
  const presetConfig = getPreset(selectedPreset);
@@ -6406,7 +6830,7 @@ ${pc10.bold("Current Configuration:")}
6406
6830
  }
6407
6831
  case "archiving": {
6408
6832
  if (config2.emailArchiving?.enabled) {
6409
- const archivingAction = await clack9.select({
6833
+ const archivingAction = await clack11.select({
6410
6834
  message: "What would you like to do with email archiving?",
6411
6835
  options: [
6412
6836
  {
@@ -6421,17 +6845,17 @@ ${pc10.bold("Current Configuration:")}
6421
6845
  }
6422
6846
  ]
6423
6847
  });
6424
- if (clack9.isCancel(archivingAction)) {
6425
- clack9.cancel("Upgrade cancelled.");
6848
+ if (clack11.isCancel(archivingAction)) {
6849
+ clack11.cancel("Upgrade cancelled.");
6426
6850
  process.exit(0);
6427
6851
  }
6428
6852
  if (archivingAction === "disable") {
6429
- const confirmDisable = await clack9.confirm({
6853
+ const confirmDisable = await clack11.confirm({
6430
6854
  message: "Are you sure? Existing archived emails will remain, but new emails won't be archived.",
6431
6855
  initialValue: false
6432
6856
  });
6433
- if (clack9.isCancel(confirmDisable) || !confirmDisable) {
6434
- clack9.cancel("Archiving not disabled.");
6857
+ if (clack11.isCancel(confirmDisable) || !confirmDisable) {
6858
+ clack11.cancel("Archiving not disabled.");
6435
6859
  process.exit(0);
6436
6860
  }
6437
6861
  updatedConfig = {
@@ -6442,7 +6866,7 @@ ${pc10.bold("Current Configuration:")}
6442
6866
  }
6443
6867
  };
6444
6868
  } else {
6445
- const retention = await clack9.select({
6869
+ const retention = await clack11.select({
6446
6870
  message: "Email archive retention period:",
6447
6871
  options: [
6448
6872
  {
@@ -6478,8 +6902,8 @@ ${pc10.bold("Current Configuration:")}
6478
6902
  ],
6479
6903
  initialValue: config2.emailArchiving.retention
6480
6904
  });
6481
- if (clack9.isCancel(retention)) {
6482
- clack9.cancel("Upgrade cancelled.");
6905
+ if (clack11.isCancel(retention)) {
6906
+ clack11.cancel("Upgrade cancelled.");
6483
6907
  process.exit(0);
6484
6908
  }
6485
6909
  updatedConfig = {
@@ -6491,19 +6915,19 @@ ${pc10.bold("Current Configuration:")}
6491
6915
  };
6492
6916
  }
6493
6917
  } else {
6494
- const enableArchiving = await clack9.confirm({
6918
+ const enableArchiving = await clack11.confirm({
6495
6919
  message: "Enable email archiving? (Store full email content with HTML for viewing)",
6496
6920
  initialValue: true
6497
6921
  });
6498
- if (clack9.isCancel(enableArchiving)) {
6499
- clack9.cancel("Upgrade cancelled.");
6922
+ if (clack11.isCancel(enableArchiving)) {
6923
+ clack11.cancel("Upgrade cancelled.");
6500
6924
  process.exit(0);
6501
6925
  }
6502
6926
  if (!enableArchiving) {
6503
- clack9.log.info("Email archiving not enabled.");
6927
+ clack11.log.info("Email archiving not enabled.");
6504
6928
  process.exit(0);
6505
6929
  }
6506
- const retention = await clack9.select({
6930
+ const retention = await clack11.select({
6507
6931
  message: "Email archive retention period:",
6508
6932
  options: [
6509
6933
  {
@@ -6539,17 +6963,17 @@ ${pc10.bold("Current Configuration:")}
6539
6963
  ],
6540
6964
  initialValue: "90days"
6541
6965
  });
6542
- if (clack9.isCancel(retention)) {
6543
- clack9.cancel("Upgrade cancelled.");
6966
+ if (clack11.isCancel(retention)) {
6967
+ clack11.cancel("Upgrade cancelled.");
6544
6968
  process.exit(0);
6545
6969
  }
6546
- clack9.log.info(
6547
- pc10.dim(
6970
+ clack11.log.info(
6971
+ pc12.dim(
6548
6972
  "Archiving stores full RFC 822 emails with HTML, attachments, and headers"
6549
6973
  )
6550
6974
  );
6551
- clack9.log.info(
6552
- pc10.dim(
6975
+ clack11.log.info(
6976
+ pc12.dim(
6553
6977
  "Cost: $2/GB ingestion + $0.19/GB/month storage (~50KB per email)"
6554
6978
  )
6555
6979
  );
@@ -6566,11 +6990,11 @@ ${pc10.bold("Current Configuration:")}
6566
6990
  }
6567
6991
  case "tracking-domain": {
6568
6992
  if (!config2.domain) {
6569
- clack9.log.error(
6993
+ clack11.log.error(
6570
6994
  "No sending domain configured. You must configure a sending domain before adding a custom tracking domain."
6571
6995
  );
6572
- clack9.log.info(
6573
- `Use ${pc10.cyan("wraps email init")} to set up a sending domain first.`
6996
+ clack11.log.info(
6997
+ `Use ${pc12.cyan("wraps email init")} to set up a sending domain first.`
6574
6998
  );
6575
6999
  process.exit(1);
6576
7000
  }
@@ -6581,21 +7005,21 @@ ${pc10.bold("Current Configuration:")}
6581
7005
  );
6582
7006
  const sendingDomain = domains.find((d) => d.domain === config2.domain);
6583
7007
  if (!sendingDomain?.verified) {
6584
- clack9.log.error(
6585
- `Sending domain ${pc10.cyan(config2.domain)} is not verified.`
7008
+ clack11.log.error(
7009
+ `Sending domain ${pc12.cyan(config2.domain)} is not verified.`
6586
7010
  );
6587
- clack9.log.info(
7011
+ clack11.log.info(
6588
7012
  "You must verify your sending domain before adding a custom tracking domain."
6589
7013
  );
6590
- clack9.log.info(
6591
- `Use ${pc10.cyan("wraps verify")} to check DNS records and complete verification.`
7014
+ clack11.log.info(
7015
+ `Use ${pc12.cyan("wraps email verify")} to check DNS records and complete verification.`
6592
7016
  );
6593
7017
  process.exit(1);
6594
7018
  }
6595
7019
  progress.info(
6596
- `Sending domain ${pc10.cyan(config2.domain)} is verified ${pc10.green("\u2713")}`
7020
+ `Sending domain ${pc12.cyan(config2.domain)} is verified ${pc12.green("\u2713")}`
6597
7021
  );
6598
- const trackingDomain = await clack9.text({
7022
+ const trackingDomain = await clack11.text({
6599
7023
  message: "Custom tracking redirect domain:",
6600
7024
  placeholder: "track.yourdomain.com",
6601
7025
  initialValue: config2.tracking?.customRedirectDomain || "",
@@ -6605,62 +7029,62 @@ ${pc10.bold("Current Configuration:")}
6605
7029
  }
6606
7030
  }
6607
7031
  });
6608
- if (clack9.isCancel(trackingDomain)) {
6609
- clack9.cancel("Upgrade cancelled.");
7032
+ if (clack11.isCancel(trackingDomain)) {
7033
+ clack11.cancel("Upgrade cancelled.");
6610
7034
  process.exit(0);
6611
7035
  }
6612
- const enableHttps = await clack9.confirm({
7036
+ const enableHttps = await clack11.confirm({
6613
7037
  message: "Enable HTTPS tracking with CloudFront + SSL certificate?",
6614
7038
  initialValue: true
6615
7039
  });
6616
- if (clack9.isCancel(enableHttps)) {
6617
- clack9.cancel("Upgrade cancelled.");
7040
+ if (clack11.isCancel(enableHttps)) {
7041
+ clack11.cancel("Upgrade cancelled.");
6618
7042
  process.exit(0);
6619
7043
  }
6620
7044
  if (enableHttps) {
6621
- clack9.log.info(
6622
- pc10.dim(
7045
+ clack11.log.info(
7046
+ pc12.dim(
6623
7047
  "HTTPS tracking creates a CloudFront distribution with an SSL certificate."
6624
7048
  )
6625
7049
  );
6626
- clack9.log.info(
6627
- pc10.dim(
7050
+ clack11.log.info(
7051
+ pc12.dim(
6628
7052
  "This ensures all tracking links use secure HTTPS connections."
6629
7053
  )
6630
7054
  );
6631
- const { findHostedZone: findHostedZone2 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
7055
+ const { findHostedZone: findHostedZone3 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
6632
7056
  const hostedZone = await progress.execute(
6633
7057
  "Checking for Route53 hosted zone",
6634
- async () => await findHostedZone2(trackingDomain || config2.domain, region)
7058
+ async () => await findHostedZone3(trackingDomain || config2.domain, region)
6635
7059
  );
6636
7060
  if (hostedZone) {
6637
7061
  progress.info(
6638
- `Found Route53 hosted zone: ${pc10.cyan(hostedZone.name)} ${pc10.green("\u2713")}`
7062
+ `Found Route53 hosted zone: ${pc12.cyan(hostedZone.name)} ${pc12.green("\u2713")}`
6639
7063
  );
6640
- clack9.log.info(
6641
- pc10.dim(
7064
+ clack11.log.info(
7065
+ pc12.dim(
6642
7066
  "DNS records (SSL certificate validation + CloudFront) will be created automatically."
6643
7067
  )
6644
7068
  );
6645
7069
  } else {
6646
- clack9.log.warn(
6647
- `No Route53 hosted zone found for ${pc10.cyan(trackingDomain || config2.domain)}`
7070
+ clack11.log.warn(
7071
+ `No Route53 hosted zone found for ${pc12.cyan(trackingDomain || config2.domain)}`
6648
7072
  );
6649
- clack9.log.info(
6650
- pc10.dim(
7073
+ clack11.log.info(
7074
+ pc12.dim(
6651
7075
  "You'll need to manually create DNS records for SSL certificate validation and CloudFront."
6652
7076
  )
6653
7077
  );
6654
- clack9.log.info(
6655
- pc10.dim("DNS record details will be shown after deployment.")
7078
+ clack11.log.info(
7079
+ pc12.dim("DNS record details will be shown after deployment.")
6656
7080
  );
6657
7081
  }
6658
- const confirmHttps = await clack9.confirm({
7082
+ const confirmHttps = await clack11.confirm({
6659
7083
  message: hostedZone ? "Proceed with automatic HTTPS setup?" : "Proceed with manual HTTPS setup (requires DNS configuration)?",
6660
7084
  initialValue: true
6661
7085
  });
6662
- if (clack9.isCancel(confirmHttps) || !confirmHttps) {
6663
- clack9.log.info("HTTPS tracking not enabled. Using HTTP tracking.");
7086
+ if (clack11.isCancel(confirmHttps) || !confirmHttps) {
7087
+ clack11.log.info("HTTPS tracking not enabled. Using HTTP tracking.");
6664
7088
  updatedConfig = {
6665
7089
  ...config2,
6666
7090
  tracking: {
@@ -6682,8 +7106,8 @@ ${pc10.bold("Current Configuration:")}
6682
7106
  };
6683
7107
  }
6684
7108
  } else {
6685
- clack9.log.info(
6686
- pc10.dim(
7109
+ clack11.log.info(
7110
+ pc12.dim(
6687
7111
  "Using HTTP tracking (standard). Links will use http:// protocol."
6688
7112
  )
6689
7113
  );
@@ -6701,7 +7125,7 @@ ${pc10.bold("Current Configuration:")}
6701
7125
  break;
6702
7126
  }
6703
7127
  case "retention": {
6704
- const retention = await clack9.select({
7128
+ const retention = await clack11.select({
6705
7129
  message: "Email history retention period (event data in DynamoDB):",
6706
7130
  options: [
6707
7131
  { value: "7days", label: "7 days", hint: "Minimal storage cost" },
@@ -6725,17 +7149,17 @@ ${pc10.bold("Current Configuration:")}
6725
7149
  ],
6726
7150
  initialValue: config2.eventTracking?.archiveRetention || "90days"
6727
7151
  });
6728
- if (clack9.isCancel(retention)) {
6729
- clack9.cancel("Upgrade cancelled.");
7152
+ if (clack11.isCancel(retention)) {
7153
+ clack11.cancel("Upgrade cancelled.");
6730
7154
  process.exit(0);
6731
7155
  }
6732
- clack9.log.info(
6733
- pc10.dim(
7156
+ clack11.log.info(
7157
+ pc12.dim(
6734
7158
  "Note: This is for event data (sent, delivered, opened, etc.) stored in DynamoDB."
6735
7159
  )
6736
7160
  );
6737
- clack9.log.info(
6738
- pc10.dim(
7161
+ clack11.log.info(
7162
+ pc12.dim(
6739
7163
  "For full email content storage, use 'Enable email archiving' option."
6740
7164
  )
6741
7165
  );
@@ -6752,7 +7176,7 @@ ${pc10.bold("Current Configuration:")}
6752
7176
  break;
6753
7177
  }
6754
7178
  case "events": {
6755
- const selectedEvents = await clack9.multiselect({
7179
+ const selectedEvents = await clack11.multiselect({
6756
7180
  message: "Select SES event types to track:",
6757
7181
  options: [
6758
7182
  { value: "SEND", label: "Send", hint: "Email sent to SES" },
@@ -6796,8 +7220,8 @@ ${pc10.bold("Current Configuration:")}
6796
7220
  ],
6797
7221
  required: true
6798
7222
  });
6799
- if (clack9.isCancel(selectedEvents)) {
6800
- clack9.cancel("Upgrade cancelled.");
7223
+ if (clack11.isCancel(selectedEvents)) {
7224
+ clack11.cancel("Upgrade cancelled.");
6801
7225
  process.exit(0);
6802
7226
  }
6803
7227
  updatedConfig = {
@@ -6812,16 +7236,16 @@ ${pc10.bold("Current Configuration:")}
6812
7236
  break;
6813
7237
  }
6814
7238
  case "dedicated-ip": {
6815
- const confirmed = await clack9.confirm({
7239
+ const confirmed = await clack11.confirm({
6816
7240
  message: "Enable dedicated IP? (Requires 100k+ emails/day, adds ~$50-100/mo)",
6817
7241
  initialValue: false
6818
7242
  });
6819
- if (clack9.isCancel(confirmed)) {
6820
- clack9.cancel("Upgrade cancelled.");
7243
+ if (clack11.isCancel(confirmed)) {
7244
+ clack11.cancel("Upgrade cancelled.");
6821
7245
  process.exit(0);
6822
7246
  }
6823
7247
  if (!confirmed) {
6824
- clack9.log.info("Dedicated IP not enabled.");
7248
+ clack11.log.info("Dedicated IP not enabled.");
6825
7249
  process.exit(0);
6826
7250
  }
6827
7251
  updatedConfig = {
@@ -6842,28 +7266,28 @@ ${pc10.bold("Current Configuration:")}
6842
7266
  const newCostData = calculateCosts(updatedConfig, 5e4);
6843
7267
  const costDiff = newCostData.total.monthly - currentCostData.total.monthly;
6844
7268
  console.log(`
6845
- ${pc10.bold("Cost Impact:")}`);
7269
+ ${pc12.bold("Cost Impact:")}`);
6846
7270
  console.log(
6847
- ` Current: ${pc10.cyan(`${formatCost(currentCostData.total.monthly)}/mo`)}`
7271
+ ` Current: ${pc12.cyan(`${formatCost(currentCostData.total.monthly)}/mo`)}`
6848
7272
  );
6849
7273
  console.log(
6850
- ` New: ${pc10.cyan(`${formatCost(newCostData.total.monthly)}/mo`)}`
7274
+ ` New: ${pc12.cyan(`${formatCost(newCostData.total.monthly)}/mo`)}`
6851
7275
  );
6852
7276
  if (costDiff > 0) {
6853
- console.log(` Change: ${pc10.yellow(`+${formatCost(costDiff)}/mo`)}`);
7277
+ console.log(` Change: ${pc12.yellow(`+${formatCost(costDiff)}/mo`)}`);
6854
7278
  } else if (costDiff < 0) {
6855
7279
  console.log(
6856
- ` Change: ${pc10.green(`${formatCost(Math.abs(costDiff))}/mo`)}`
7280
+ ` Change: ${pc12.green(`${formatCost(Math.abs(costDiff))}/mo`)}`
6857
7281
  );
6858
7282
  }
6859
7283
  console.log("");
6860
7284
  if (!(options.yes || options.preview)) {
6861
- const confirmed = await clack9.confirm({
7285
+ const confirmed = await clack11.confirm({
6862
7286
  message: "Proceed with upgrade?",
6863
7287
  initialValue: true
6864
7288
  });
6865
- if (clack9.isCancel(confirmed) || !confirmed) {
6866
- clack9.cancel("Upgrade cancelled.");
7289
+ if (clack11.isCancel(confirmed) || !confirmed) {
7290
+ clack11.cancel("Upgrade cancelled.");
6867
7291
  process.exit(0);
6868
7292
  }
6869
7293
  }
@@ -6885,7 +7309,7 @@ ${pc10.bold("Cost Impact:")}`);
6885
7309
  "Generating upgrade preview",
6886
7310
  async () => {
6887
7311
  await ensurePulumiWorkDir();
6888
- const stack = await pulumi9.automation.LocalWorkspace.createOrSelectStack(
7312
+ const stack = await pulumi11.automation.LocalWorkspace.createOrSelectStack(
6889
7313
  {
6890
7314
  stackName: metadata.services.email?.pulumiStackName || `wraps-${identity.accountId}-${region}`,
6891
7315
  projectName: "wraps-email",
@@ -6935,8 +7359,8 @@ ${pc10.bold("Cost Impact:")}`);
6935
7359
  costEstimate: costComparison,
6936
7360
  commandName: "wraps email upgrade"
6937
7361
  });
6938
- clack9.outro(
6939
- pc10.green("Preview complete. Run without --preview to upgrade.")
7362
+ clack11.outro(
7363
+ pc12.green("Preview complete. Run without --preview to upgrade.")
6940
7364
  );
6941
7365
  trackServiceUpgrade("email", {
6942
7366
  from_preset: metadata.services.email?.preset,
@@ -6960,7 +7384,7 @@ ${pc10.bold("Cost Impact:")}`);
6960
7384
  "Updating Wraps infrastructure (this may take 2-3 minutes)",
6961
7385
  async () => {
6962
7386
  await ensurePulumiWorkDir();
6963
- const stack = await pulumi9.automation.LocalWorkspace.createOrSelectStack(
7387
+ const stack = await pulumi11.automation.LocalWorkspace.createOrSelectStack(
6964
7388
  {
6965
7389
  stackName: metadata.services.email?.pulumiStackName || `wraps-${identity.accountId}-${region}`,
6966
7390
  projectName: "wraps-email",
@@ -7035,8 +7459,8 @@ ${pc10.bold("Cost Impact:")}`);
7035
7459
  throw new Error(`Pulumi upgrade failed: ${error.message}`);
7036
7460
  }
7037
7461
  if (outputs.domain && outputs.dkimTokens && outputs.dkimTokens.length > 0) {
7038
- const { findHostedZone: findHostedZone2, createDNSRecords: createDNSRecords2 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
7039
- const hostedZone = await findHostedZone2(outputs.domain, region);
7462
+ const { findHostedZone: findHostedZone3, createDNSRecords: createDNSRecords2 } = await Promise.resolve().then(() => (init_route53(), route53_exports));
7463
+ const hostedZone = await findHostedZone3(outputs.domain, region);
7040
7464
  if (hostedZone) {
7041
7465
  try {
7042
7466
  progress.start("Creating DNS records in Route53");
@@ -7101,21 +7525,21 @@ ${pc10.bold("Cost Impact:")}`);
7101
7525
  httpsTrackingEnabled: outputs.httpsTrackingEnabled
7102
7526
  });
7103
7527
  console.log(`
7104
- ${pc10.green("\u2713")} ${pc10.bold("Upgrade complete!")}
7528
+ ${pc12.green("\u2713")} ${pc12.bold("Upgrade complete!")}
7105
7529
  `);
7106
7530
  if (upgradeAction === "preset" && newPreset) {
7107
7531
  console.log(
7108
- `Upgraded to ${pc10.cyan(newPreset)} preset (${pc10.green(`${formatCost(newCostData.total.monthly)}/mo`)})
7532
+ `Upgraded to ${pc12.cyan(newPreset)} preset (${pc12.green(`${formatCost(newCostData.total.monthly)}/mo`)})
7109
7533
  `
7110
7534
  );
7111
7535
  } else {
7112
7536
  console.log(
7113
- `Updated configuration (${pc10.green(`${formatCost(newCostData.total.monthly)}/mo`)})
7537
+ `Updated configuration (${pc12.green(`${formatCost(newCostData.total.monthly)}/mo`)})
7114
7538
  `
7115
7539
  );
7116
7540
  }
7117
7541
  if (needsCertificateValidation) {
7118
- console.log(pc10.bold("\u26A0\uFE0F HTTPS Tracking - Next Steps:\n"));
7542
+ console.log(pc12.bold("\u26A0\uFE0F HTTPS Tracking - Next Steps:\n"));
7119
7543
  console.log(
7120
7544
  " 1. Add the SSL certificate validation DNS record shown above to your DNS provider"
7121
7545
  );
@@ -7123,17 +7547,17 @@ ${pc10.green("\u2713")} ${pc10.bold("Upgrade complete!")}
7123
7547
  " 2. Wait for DNS propagation and certificate validation (5-30 minutes)"
7124
7548
  );
7125
7549
  console.log(
7126
- ` 3. Run ${pc10.cyan("wraps email upgrade")} again to complete CloudFront setup
7550
+ ` 3. Run ${pc12.cyan("wraps email upgrade")} again to complete CloudFront setup
7127
7551
  `
7128
7552
  );
7129
7553
  console.log(
7130
- pc10.dim(
7554
+ pc12.dim(
7131
7555
  " Note: CloudFront distribution will be created once the certificate is validated.\n"
7132
7556
  )
7133
7557
  );
7134
7558
  } else if (outputs.httpsTrackingEnabled && outputs.cloudFrontDomain) {
7135
7559
  console.log(
7136
- pc10.green("\u2713") + " " + pc10.bold("HTTPS tracking is fully configured and ready to use!\n")
7560
+ pc12.green("\u2713") + " " + pc12.bold("HTTPS tracking is fully configured and ready to use!\n")
7137
7561
  );
7138
7562
  }
7139
7563
  const enabledFeatures = [];
@@ -7158,11 +7582,11 @@ ${pc10.green("\u2713")} ${pc10.bold("Upgrade complete!")}
7158
7582
 
7159
7583
  // src/commands/shared/dashboard.ts
7160
7584
  init_esm_shims();
7161
- import * as clack10 from "@clack/prompts";
7162
- import * as pulumi10 from "@pulumi/pulumi";
7585
+ import * as clack12 from "@clack/prompts";
7586
+ import * as pulumi12 from "@pulumi/pulumi";
7163
7587
  import getPort from "get-port";
7164
7588
  import open from "open";
7165
- import pc11 from "picocolors";
7589
+ import pc13 from "picocolors";
7166
7590
 
7167
7591
  // src/console/server.ts
7168
7592
  init_esm_shims();
@@ -8387,7 +8811,7 @@ async function startConsoleServer(config2) {
8387
8811
  init_events();
8388
8812
  init_aws();
8389
8813
  async function dashboard(options) {
8390
- clack10.intro(pc11.bold("Wraps Dashboard"));
8814
+ clack12.intro(pc13.bold("Wraps Dashboard"));
8391
8815
  const progress = new DeploymentProgress();
8392
8816
  const identity = await progress.execute(
8393
8817
  "Validating AWS credentials",
@@ -8397,16 +8821,16 @@ async function dashboard(options) {
8397
8821
  let stackOutputs = {};
8398
8822
  try {
8399
8823
  await ensurePulumiWorkDir();
8400
- const stack = await pulumi10.automation.LocalWorkspace.selectStack({
8824
+ const stack = await pulumi12.automation.LocalWorkspace.selectStack({
8401
8825
  stackName: `wraps-${identity.accountId}-${region}`,
8402
8826
  workDir: getPulumiWorkDir()
8403
8827
  });
8404
8828
  stackOutputs = await stack.outputs();
8405
8829
  } catch (_error) {
8406
8830
  progress.stop();
8407
- clack10.log.error("No Wraps infrastructure found");
8831
+ clack12.log.error("No Wraps infrastructure found");
8408
8832
  console.log(
8409
- `\\nRun ${pc11.cyan("wraps email init")} to deploy infrastructure first.\\n`
8833
+ `\\nRun ${pc13.cyan("wraps email init")} to deploy infrastructure first.\\n`
8410
8834
  );
8411
8835
  process.exit(1);
8412
8836
  }
@@ -8415,9 +8839,9 @@ async function dashboard(options) {
8415
8839
  const archivingEnabled = stackOutputs.archivingEnabled?.value ?? false;
8416
8840
  const port = options.port || await getPort({ port: [5555, 5556, 5557, 5558, 5559] });
8417
8841
  progress.stop();
8418
- clack10.log.success("Starting dashboard server...");
8842
+ clack12.log.success("Starting dashboard server...");
8419
8843
  console.log(
8420
- `${pc11.dim("Using current AWS credentials (no role assumption)")}\\n`
8844
+ `${pc13.dim("Using current AWS credentials (no role assumption)")}\\n`
8421
8845
  );
8422
8846
  const { url } = await startConsoleServer({
8423
8847
  port,
@@ -8430,8 +8854,8 @@ async function dashboard(options) {
8430
8854
  archiveArn,
8431
8855
  archivingEnabled
8432
8856
  });
8433
- console.log(`\\n${pc11.bold("Dashboard:")} ${pc11.cyan(url)}`);
8434
- console.log(`${pc11.dim("Press Ctrl+C to stop")}\\n`);
8857
+ console.log(`\\n${pc13.bold("Dashboard:")} ${pc13.cyan(url)}`);
8858
+ console.log(`${pc13.dim("Press Ctrl+C to stop")}\\n`);
8435
8859
  if (!options.noOpen) {
8436
8860
  await open(url);
8437
8861
  }
@@ -8446,210 +8870,147 @@ async function dashboard(options) {
8446
8870
 
8447
8871
  // src/commands/shared/destroy.ts
8448
8872
  init_esm_shims();
8449
- init_events();
8450
8873
  init_aws();
8451
- import * as clack11 from "@clack/prompts";
8452
- import * as pulumi11 from "@pulumi/pulumi";
8453
- import pc12 from "picocolors";
8874
+ import * as clack13 from "@clack/prompts";
8875
+ import pc14 from "picocolors";
8454
8876
  async function destroy(options) {
8455
- const startTime = Date.now();
8456
- clack11.intro(
8457
- pc12.bold(
8458
- options.preview ? "Wraps Destruction Preview" : "Wraps Email Infrastructure Teardown"
8459
- )
8460
- );
8461
- const progress = new DeploymentProgress();
8462
- const identity = await progress.execute(
8463
- "Validating AWS credentials",
8464
- async () => validateAWSCredentials()
8465
- );
8877
+ clack13.intro(pc14.bold("Wraps Infrastructure Teardown"));
8878
+ const spinner3 = clack13.spinner();
8879
+ spinner3.start("Validating AWS credentials");
8880
+ let identity;
8881
+ try {
8882
+ identity = await validateAWSCredentials();
8883
+ spinner3.stop("AWS credentials validated");
8884
+ } catch (error) {
8885
+ spinner3.stop("AWS credentials validation failed");
8886
+ throw error;
8887
+ }
8466
8888
  const region = await getAWSRegion();
8467
- if (!(options.force || options.preview)) {
8468
- const confirmed = await clack11.confirm({
8469
- message: pc12.red(
8470
- "Are you sure you want to destroy all Wraps infrastructure?"
8471
- ),
8472
- initialValue: false
8473
- });
8474
- if (clack11.isCancel(confirmed) || !confirmed) {
8475
- clack11.cancel("Destruction cancelled.");
8476
- process.exit(0);
8477
- }
8889
+ const metadata = await loadConnectionMetadata(identity.accountId, region);
8890
+ const deployedServices = [];
8891
+ if (metadata?.services?.email) {
8892
+ deployedServices.push("email");
8478
8893
  }
8479
- if (options.preview) {
8480
- try {
8481
- const previewResult = await progress.execute(
8482
- "Generating destruction preview",
8483
- async () => {
8484
- await ensurePulumiWorkDir();
8485
- const stackName = `wraps-${identity.accountId}-${region}`;
8486
- let stack;
8487
- try {
8488
- stack = await pulumi11.automation.LocalWorkspace.selectStack({
8489
- stackName,
8490
- workDir: getPulumiWorkDir()
8491
- });
8492
- } catch (_error) {
8493
- throw new Error("No Wraps infrastructure found to preview");
8494
- }
8495
- const result = await stack.preview({ diff: true });
8496
- return result;
8497
- }
8498
- );
8499
- displayPreview({
8500
- changeSummary: previewResult.changeSummary,
8501
- costEstimate: "Monthly cost after destruction: $0.00",
8502
- commandName: "wraps destroy"
8503
- });
8504
- clack11.outro(
8505
- pc12.green("Preview complete. Run without --preview to destroy.")
8506
- );
8507
- trackServiceRemoved("email", {
8508
- preview: true,
8509
- duration_ms: Date.now() - startTime
8510
- });
8894
+ if (deployedServices.length === 0) {
8895
+ clack13.log.warn("No Wraps services found in this region");
8896
+ console.log(
8897
+ `
8898
+ Run ${pc14.cyan("wraps email init")} to deploy infrastructure.
8899
+ `
8900
+ );
8901
+ process.exit(0);
8902
+ }
8903
+ if (deployedServices.length === 1) {
8904
+ const service = deployedServices[0];
8905
+ clack13.log.info(`Found ${pc14.cyan(service)} service deployed`);
8906
+ if (service === "email") {
8907
+ await emailDestroy(options);
8511
8908
  return;
8512
- } catch (error) {
8513
- progress.stop();
8514
- if (error.message.includes("No Wraps infrastructure found")) {
8515
- clack11.log.warn("No Wraps infrastructure found to preview");
8516
- process.exit(0);
8517
- }
8518
- trackError("PREVIEW_FAILED", "destroy", { step: "preview" });
8519
- throw new Error(`Preview failed: ${error.message}`);
8520
8909
  }
8521
8910
  }
8522
- try {
8523
- await progress.execute(
8524
- "Destroying infrastructure (this may take 2-3 minutes)",
8525
- async () => {
8526
- await ensurePulumiWorkDir();
8527
- const stackName = `wraps-${identity.accountId}-${region}`;
8528
- let stack;
8529
- try {
8530
- stack = await pulumi11.automation.LocalWorkspace.selectStack({
8531
- stackName,
8532
- workDir: getPulumiWorkDir()
8533
- });
8534
- } catch (_error) {
8535
- throw new Error("No Wraps infrastructure found to destroy");
8536
- }
8537
- await stack.destroy({ onOutput: () => {
8538
- } });
8539
- await stack.workspace.removeStack(stackName);
8911
+ const serviceToDestroy = await clack13.select({
8912
+ message: "Which service would you like to destroy?",
8913
+ options: [
8914
+ ...deployedServices.map((s) => ({
8915
+ value: s,
8916
+ label: s.charAt(0).toUpperCase() + s.slice(1),
8917
+ hint: s === "email" ? "AWS SES email infrastructure" : void 0
8918
+ })),
8919
+ {
8920
+ value: "all",
8921
+ label: "All services",
8922
+ hint: "Destroy all Wraps infrastructure"
8540
8923
  }
8541
- );
8542
- } catch (error) {
8543
- progress.stop();
8544
- if (error.message.includes("No Wraps infrastructure found")) {
8545
- clack11.log.warn("No Wraps infrastructure found");
8546
- await deleteConnectionMetadata(identity.accountId, region);
8547
- process.exit(0);
8924
+ ]
8925
+ });
8926
+ if (clack13.isCancel(serviceToDestroy)) {
8927
+ clack13.cancel("Operation cancelled.");
8928
+ process.exit(0);
8929
+ }
8930
+ if (serviceToDestroy === "email" || serviceToDestroy === "all") {
8931
+ if (deployedServices.includes("email")) {
8932
+ await emailDestroy(options);
8548
8933
  }
8549
- trackError("DESTROY_FAILED", "destroy", { step: "destroy" });
8550
- clack11.log.error("Infrastructure destruction failed");
8551
- throw error;
8552
8934
  }
8553
- await deleteConnectionMetadata(identity.accountId, region);
8554
- progress.stop();
8555
- clack11.outro(pc12.green("All Wraps infrastructure has been removed"));
8556
- console.log(
8557
- `
8558
- Run ${pc12.cyan("wraps email init")} to deploy infrastructure again.
8559
- `
8560
- );
8561
- trackServiceRemoved("email", {
8562
- reason: "user_initiated",
8563
- duration_ms: Date.now() - startTime
8564
- });
8935
+ if (serviceToDestroy === "all") {
8936
+ clack13.outro(pc14.green("All Wraps infrastructure has been removed"));
8937
+ }
8565
8938
  }
8566
8939
 
8567
8940
  // src/commands/shared/status.ts
8568
8941
  init_esm_shims();
8569
8942
  init_events();
8570
8943
  init_aws();
8571
- import * as clack12 from "@clack/prompts";
8572
- import * as pulumi12 from "@pulumi/pulumi";
8573
- import pc13 from "picocolors";
8944
+ import * as clack14 from "@clack/prompts";
8945
+ import * as pulumi13 from "@pulumi/pulumi";
8946
+ import pc15 from "picocolors";
8574
8947
  async function status(_options) {
8575
8948
  const startTime = Date.now();
8576
8949
  const progress = new DeploymentProgress();
8950
+ clack14.intro(pc15.bold("Wraps Infrastructure Status"));
8577
8951
  const identity = await progress.execute(
8578
8952
  "Loading infrastructure status",
8579
8953
  async () => validateAWSCredentials()
8580
8954
  );
8955
+ progress.info(`AWS Account: ${pc15.cyan(identity.accountId)}`);
8581
8956
  const region = await getAWSRegion();
8582
- let stackOutputs = {};
8957
+ progress.info(`Region: ${pc15.cyan(region)}`);
8958
+ const services = [];
8583
8959
  try {
8584
8960
  await ensurePulumiWorkDir();
8585
- const stack = await pulumi12.automation.LocalWorkspace.selectStack({
8961
+ const stack = await pulumi13.automation.LocalWorkspace.selectStack({
8586
8962
  stackName: `wraps-${identity.accountId}-${region}`,
8587
8963
  workDir: getPulumiWorkDir()
8588
8964
  });
8589
- stackOutputs = await stack.outputs();
8965
+ const outputs = await stack.outputs();
8966
+ if (outputs.roleArn?.value) {
8967
+ const domainCount = outputs.domains?.value?.length || 0;
8968
+ services.push({
8969
+ name: "Email",
8970
+ status: "deployed",
8971
+ details: domainCount > 0 ? `${domainCount} domain(s)` : void 0
8972
+ });
8973
+ } else {
8974
+ services.push({ name: "Email", status: "not_deployed" });
8975
+ }
8590
8976
  } catch (_error) {
8591
- progress.stop();
8592
- clack12.log.error("No Wraps infrastructure found");
8977
+ services.push({ name: "Email", status: "not_deployed" });
8978
+ }
8979
+ progress.stop();
8980
+ console.log();
8981
+ clack14.note(
8982
+ services.map((s) => {
8983
+ if (s.status === "deployed") {
8984
+ const details = s.details ? pc15.dim(` (${s.details})`) : "";
8985
+ return ` ${pc15.green("\u2713")} ${s.name}${details}`;
8986
+ }
8987
+ return ` ${pc15.dim("\u25CB")} ${s.name} ${pc15.dim("(not deployed)")}`;
8988
+ }).join("\n"),
8989
+ "Services"
8990
+ );
8991
+ const hasDeployedServices = services.some((s) => s.status === "deployed");
8992
+ if (hasDeployedServices) {
8993
+ console.log(`
8994
+ ${pc15.bold("Details:")}`);
8995
+ if (services.find((s) => s.name === "Email")?.status === "deployed") {
8996
+ console.log(
8997
+ ` ${pc15.dim("Email:")} ${pc15.cyan("wraps email status")}`
8998
+ );
8999
+ }
9000
+ } else {
9001
+ console.log(`
9002
+ ${pc15.bold("Get started:")}`);
8593
9003
  console.log(
8594
- `
8595
- Run ${pc13.cyan("wraps email init")} to deploy infrastructure.
8596
- `
9004
+ ` ${pc15.dim("Deploy email:")} ${pc15.cyan("wraps email init")}`
8597
9005
  );
8598
- process.exit(1);
8599
9006
  }
8600
- const domains = await listSESDomains(region);
8601
- const { SESv2Client: SESv2Client5, GetEmailIdentityCommand: GetEmailIdentityCommand4 } = await import("@aws-sdk/client-sesv2");
8602
- const sesv2Client = new SESv2Client5({ region });
8603
- const domainsWithTokens = await Promise.all(
8604
- domains.map(async (d) => {
8605
- try {
8606
- const identity2 = await sesv2Client.send(
8607
- new GetEmailIdentityCommand4({ EmailIdentity: d.domain })
8608
- );
8609
- return {
8610
- domain: d.domain,
8611
- status: d.verified ? "verified" : "pending",
8612
- dkimTokens: identity2.DkimAttributes?.Tokens || [],
8613
- mailFromDomain: identity2.MailFromAttributes?.MailFromDomain,
8614
- mailFromStatus: identity2.MailFromAttributes?.MailFromDomainStatus
8615
- };
8616
- } catch (_error) {
8617
- return {
8618
- domain: d.domain,
8619
- status: d.verified ? "verified" : "pending",
8620
- dkimTokens: void 0,
8621
- mailFromDomain: void 0,
8622
- mailFromStatus: void 0
8623
- };
8624
- }
8625
- })
8626
- );
8627
- const integrationLevel = stackOutputs.configSetName ? "enhanced" : "dashboard-only";
8628
- progress.stop();
8629
- displayStatus({
8630
- integrationLevel,
8631
- region,
8632
- domains: domainsWithTokens,
8633
- resources: {
8634
- roleArn: stackOutputs.roleArn?.value,
8635
- configSetName: stackOutputs.configSetName?.value,
8636
- tableName: stackOutputs.tableName?.value,
8637
- lambdaFunctions: stackOutputs.lambdaFunctions?.value?.length || 0,
8638
- snsTopics: integrationLevel === "enhanced" ? 1 : 0,
8639
- archiveArn: stackOutputs.archiveArn?.value,
8640
- archivingEnabled: stackOutputs.archivingEnabled?.value,
8641
- archiveRetention: stackOutputs.archiveRetention?.value
8642
- },
8643
- tracking: stackOutputs.customTrackingDomain?.value ? {
8644
- customTrackingDomain: stackOutputs.customTrackingDomain?.value,
8645
- httpsEnabled: stackOutputs.httpsTrackingEnabled?.value,
8646
- cloudFrontDomain: stackOutputs.cloudFrontDomain?.value
8647
- } : void 0
8648
- });
9007
+ console.log(`
9008
+ ${pc15.bold("Dashboard:")} ${pc15.blue("https://app.wraps.dev")}`);
9009
+ console.log(`${pc15.bold("Docs:")} ${pc15.blue("https://wraps.dev/docs")}
9010
+ `);
8649
9011
  trackCommand("status", {
8650
9012
  success: true,
8651
- domain_count: domainsWithTokens.length,
8652
- integration_level: integrationLevel,
9013
+ services_deployed: services.filter((s) => s.status === "deployed").length,
8653
9014
  duration_ms: Date.now() - startTime
8654
9015
  });
8655
9016
  }
@@ -8657,56 +9018,56 @@ Run ${pc13.cyan("wraps email init")} to deploy infrastructure.
8657
9018
  // src/commands/telemetry.ts
8658
9019
  init_esm_shims();
8659
9020
  init_client();
8660
- import * as clack13 from "@clack/prompts";
8661
- import pc14 from "picocolors";
9021
+ import * as clack15 from "@clack/prompts";
9022
+ import pc16 from "picocolors";
8662
9023
  async function telemetryEnable() {
8663
9024
  const client = getTelemetryClient();
8664
9025
  client.enable();
8665
- clack13.log.success(pc14.green("Telemetry enabled"));
8666
- console.log(` Config: ${pc14.dim(client.getConfigPath())}`);
9026
+ clack15.log.success(pc16.green("Telemetry enabled"));
9027
+ console.log(` Config: ${pc16.dim(client.getConfigPath())}`);
8667
9028
  console.log(`
8668
- ${pc14.dim("Thank you for helping improve Wraps!")}
9029
+ ${pc16.dim("Thank you for helping improve Wraps!")}
8669
9030
  `);
8670
9031
  }
8671
9032
  async function telemetryDisable() {
8672
9033
  const client = getTelemetryClient();
8673
9034
  client.disable();
8674
- clack13.log.success(pc14.green("Telemetry disabled"));
8675
- console.log(` Config: ${pc14.dim(client.getConfigPath())}`);
9035
+ clack15.log.success(pc16.green("Telemetry disabled"));
9036
+ console.log(` Config: ${pc16.dim(client.getConfigPath())}`);
8676
9037
  console.log(
8677
9038
  `
8678
- ${pc14.dim("You can re-enable with:")} wraps telemetry enable
9039
+ ${pc16.dim("You can re-enable with:")} wraps telemetry enable
8679
9040
  `
8680
9041
  );
8681
9042
  }
8682
9043
  async function telemetryStatus() {
8683
9044
  const client = getTelemetryClient();
8684
- clack13.intro(pc14.bold("Telemetry Status"));
8685
- const status2 = client.isEnabled() ? pc14.green("Enabled") : pc14.red("Disabled");
9045
+ clack15.intro(pc16.bold("Telemetry Status"));
9046
+ const status2 = client.isEnabled() ? pc16.green("Enabled") : pc16.red("Disabled");
8686
9047
  console.log();
8687
- console.log(` ${pc14.bold("Status:")} ${status2}`);
8688
- console.log(` ${pc14.bold("Config file:")} ${pc14.dim(client.getConfigPath())}`);
9048
+ console.log(` ${pc16.bold("Status:")} ${status2}`);
9049
+ console.log(` ${pc16.bold("Config file:")} ${pc16.dim(client.getConfigPath())}`);
8689
9050
  if (client.isEnabled()) {
8690
9051
  console.log();
8691
- console.log(pc14.bold(" How to opt-out:"));
8692
- console.log(` ${pc14.cyan("wraps telemetry disable")}`);
9052
+ console.log(pc16.bold(" How to opt-out:"));
9053
+ console.log(` ${pc16.cyan("wraps telemetry disable")}`);
8693
9054
  console.log(
8694
- ` ${pc14.dim("Or set:")} ${pc14.cyan("WRAPS_TELEMETRY_DISABLED=1")}`
9055
+ ` ${pc16.dim("Or set:")} ${pc16.cyan("WRAPS_TELEMETRY_DISABLED=1")}`
8695
9056
  );
8696
- console.log(` ${pc14.dim("Or set:")} ${pc14.cyan("DO_NOT_TRACK=1")}`);
9057
+ console.log(` ${pc16.dim("Or set:")} ${pc16.cyan("DO_NOT_TRACK=1")}`);
8697
9058
  } else {
8698
9059
  console.log();
8699
- console.log(pc14.bold(" How to opt-in:"));
8700
- console.log(` ${pc14.cyan("wraps telemetry enable")}`);
9060
+ console.log(pc16.bold(" How to opt-in:"));
9061
+ console.log(` ${pc16.cyan("wraps telemetry enable")}`);
8701
9062
  }
8702
9063
  console.log();
8703
- console.log(pc14.bold(" Debug mode:"));
9064
+ console.log(pc16.bold(" Debug mode:"));
8704
9065
  console.log(
8705
- ` ${pc14.dim("See what would be sent:")} ${pc14.cyan("WRAPS_TELEMETRY_DEBUG=1 wraps <command>")}`
9066
+ ` ${pc16.dim("See what would be sent:")} ${pc16.cyan("WRAPS_TELEMETRY_DEBUG=1 wraps <command>")}`
8706
9067
  );
8707
9068
  console.log();
8708
9069
  console.log(
8709
- ` ${pc14.dim("Learn more:")} ${pc14.cyan("https://wraps.dev/docs/telemetry")}`
9070
+ ` ${pc16.dim("Learn more:")} ${pc16.cyan("https://wraps.dev/docs/telemetry")}`
8710
9071
  );
8711
9072
  console.log();
8712
9073
  }
@@ -8724,19 +9085,41 @@ function printCompletionScript() {
8724
9085
  console.log("# ========================\n");
8725
9086
  console.log("# Tab completion will be available in a future release.\n");
8726
9087
  console.log("# For now, here are the available commands:\n");
8727
- console.log("# Commands:");
9088
+ console.log("# Email Commands:");
8728
9089
  console.log(
8729
9090
  "# wraps email init [--provider vercel|aws|railway|other] [--region <region>] [--domain <domain>]"
8730
9091
  );
8731
- console.log("# wraps status [--account <account-id>]");
8732
- console.log("# wraps completion\n");
9092
+ console.log("# wraps email connect [--region <region>]");
9093
+ console.log("# wraps email status [--account <account-id>]");
9094
+ console.log("# wraps email verify --domain <domain>");
9095
+ console.log("# wraps email sync");
9096
+ console.log("# wraps email upgrade");
9097
+ console.log("# wraps email restore [--region <region>] [--force]");
9098
+ console.log("# wraps email destroy [--force] [--preview]");
9099
+ console.log("# wraps email domains add --domain <domain>");
9100
+ console.log("# wraps email domains list");
9101
+ console.log("# wraps email domains verify --domain <domain>");
9102
+ console.log("# wraps email domains get-dkim --domain <domain>");
9103
+ console.log("# wraps email domains remove --domain <domain> [--force]\n");
9104
+ console.log("# Global Commands:");
9105
+ console.log("# wraps status");
9106
+ console.log("# wraps destroy [--force] [--preview]");
9107
+ console.log("# wraps console [--port <port>] [--no-open]");
9108
+ console.log("# wraps completion");
9109
+ console.log("# wraps telemetry [enable|disable|status]\n");
9110
+ console.log("# Dashboard Commands:");
9111
+ console.log("# wraps dashboard update-role [--region <region>] [--force]\n");
8733
9112
  console.log("# Flags:");
8734
- console.log("# --provider : vercel, aws, railway, other");
9113
+ console.log("# -p, --provider : vercel, aws, railway, other");
8735
9114
  console.log(
8736
- "# --region : us-east-1, us-east-2, us-west-1, us-west-2, eu-west-1, eu-west-2, etc."
9115
+ "# -r, --region : us-east-1, us-east-2, us-west-1, us-west-2, eu-west-1, eu-west-2, etc."
8737
9116
  );
8738
- console.log("# --domain : Your domain name (e.g., myapp.com)");
8739
- console.log("# --account : AWS account ID or alias\n");
9117
+ console.log("# -d, --domain : Your domain name (e.g., myapp.com)");
9118
+ console.log("# --account : AWS account ID or alias");
9119
+ console.log("# --preset : starter, production, enterprise, custom");
9120
+ console.log("# -y, --yes : Skip confirmation prompts");
9121
+ console.log("# -f, --force : Force destructive operations");
9122
+ console.log("# --preview : Preview changes without deploying\n");
8740
9123
  }
8741
9124
 
8742
9125
  // src/cli.ts
@@ -8753,62 +9136,66 @@ function showVersion() {
8753
9136
  process.exit(0);
8754
9137
  }
8755
9138
  function showHelp() {
8756
- clack14.intro(pc15.bold(`WRAPS CLI v${VERSION}`));
9139
+ clack16.intro(pc17.bold(`WRAPS CLI v${VERSION}`));
8757
9140
  console.log("Deploy AWS infrastructure to your account\n");
8758
9141
  console.log("Usage: wraps [service] <command> [options]\n");
8759
9142
  console.log("Services:");
8760
- console.log(` ${pc15.cyan("email")} Email infrastructure (AWS SES)`);
9143
+ console.log(` ${pc17.cyan("email")} Email infrastructure (AWS SES)
9144
+ `);
9145
+ console.log("Email Commands:");
8761
9146
  console.log(
8762
- ` ${pc15.cyan("sms")} SMS infrastructure (AWS End User Messaging) ${pc15.dim("[coming soon]")}
8763
- `
9147
+ ` ${pc17.cyan("email init")} Deploy new email infrastructure`
8764
9148
  );
8765
- console.log("Email Commands:");
8766
9149
  console.log(
8767
- ` ${pc15.cyan("email init")} Deploy new email infrastructure`
9150
+ ` ${pc17.cyan("email connect")} Connect to existing AWS SES`
8768
9151
  );
9152
+ console.log(` ${pc17.cyan("email status")} Show email infrastructure details`);
9153
+ console.log(` ${pc17.cyan("email verify")} Verify domain DNS records`);
8769
9154
  console.log(
8770
- ` ${pc15.cyan("email connect")} Connect to existing AWS SES`
9155
+ ` ${pc17.cyan("email sync")} Apply CLI updates to infrastructure`
8771
9156
  );
8772
- console.log(` ${pc15.cyan("email domains verify")} Verify domain DNS records`);
9157
+ console.log(` ${pc17.cyan("email upgrade")} Add features`);
8773
9158
  console.log(
8774
- ` ${pc15.cyan("email sync")} Apply CLI updates to infrastructure`
9159
+ ` ${pc17.cyan("email restore")} Restore original configuration`
8775
9160
  );
8776
- console.log(` ${pc15.cyan("email upgrade")} Add features`);
8777
9161
  console.log(
8778
- ` ${pc15.cyan("email restore")} Restore original configuration
8779
- `
9162
+ ` ${pc17.cyan("email destroy")} Remove email infrastructure`
8780
9163
  );
9164
+ console.log(` ${pc17.cyan("email domains add")} Add a domain to SES`);
9165
+ console.log(` ${pc17.cyan("email domains list")} List all domains`);
9166
+ console.log(` ${pc17.cyan("email domains remove")} Remove a domain
9167
+ `);
8781
9168
  console.log("Console & Dashboard:");
8782
- console.log(` ${pc15.cyan("console")} Start local web console`);
9169
+ console.log(` ${pc17.cyan("console")} Start local web console`);
8783
9170
  console.log(
8784
- ` ${pc15.cyan("dashboard update-role")} Update hosted dashboard IAM permissions
9171
+ ` ${pc17.cyan("dashboard update-role")} Update hosted dashboard IAM permissions
8785
9172
  `
8786
9173
  );
8787
9174
  console.log("Global Commands:");
8788
- console.log(` ${pc15.cyan("status")} Show all infrastructure status`);
8789
- console.log(` ${pc15.cyan("destroy")} Remove deployed infrastructure`);
8790
- console.log(` ${pc15.cyan("completion")} Generate shell completion script`);
9175
+ console.log(` ${pc17.cyan("status")} Show overview of all services`);
9176
+ console.log(` ${pc17.cyan("destroy")} Remove deployed infrastructure`);
9177
+ console.log(` ${pc17.cyan("completion")} Generate shell completion script`);
8791
9178
  console.log(
8792
- ` ${pc15.cyan("telemetry")} Manage anonymous telemetry settings
9179
+ ` ${pc17.cyan("telemetry")} Manage anonymous telemetry settings
8793
9180
  `
8794
9181
  );
8795
9182
  console.log("Options:");
8796
9183
  console.log(
8797
- ` ${pc15.dim("-p, --provider")} Hosting provider (vercel, aws, railway, other)`
9184
+ ` ${pc17.dim("-p, --provider")} Hosting provider (vercel, aws, railway, other)`
8798
9185
  );
8799
- console.log(` ${pc15.dim("-r, --region")} AWS region`);
8800
- console.log(` ${pc15.dim("-d, --domain")} Domain name`);
8801
- console.log(` ${pc15.dim("--account")} AWS account ID or alias`);
8802
- console.log(` ${pc15.dim("--preset")} Configuration preset`);
8803
- console.log(` ${pc15.dim("-y, --yes")} Skip confirmation prompts`);
8804
- console.log(` ${pc15.dim("-f, --force")} Force destructive operations`);
9186
+ console.log(` ${pc17.dim("-r, --region")} AWS region`);
9187
+ console.log(` ${pc17.dim("-d, --domain")} Domain name`);
9188
+ console.log(` ${pc17.dim("--account")} AWS account ID or alias`);
9189
+ console.log(` ${pc17.dim("--preset")} Configuration preset`);
9190
+ console.log(` ${pc17.dim("-y, --yes")} Skip confirmation prompts`);
9191
+ console.log(` ${pc17.dim("-f, --force")} Force destructive operations`);
8805
9192
  console.log(
8806
- ` ${pc15.dim("--preview")} Preview changes without deploying`
9193
+ ` ${pc17.dim("--preview")} Preview changes without deploying`
8807
9194
  );
8808
- console.log(` ${pc15.dim("-v, --version")} Show version number
9195
+ console.log(` ${pc17.dim("-v, --version")} Show version number
8809
9196
  `);
8810
9197
  console.log(
8811
- `Run ${pc15.cyan("wraps <service> <command> --help")} for more information.
9198
+ `Run ${pc17.cyan("wraps <service> <command> --help")} for more information.
8812
9199
  `
8813
9200
  );
8814
9201
  process.exit(0);
@@ -8875,37 +9262,9 @@ var flags = args.parse(process.argv);
8875
9262
  var [primaryCommand, subCommand] = args.sub;
8876
9263
  if (!primaryCommand) {
8877
9264
  async function selectService() {
8878
- clack14.intro(pc15.bold(`WRAPS CLI v${VERSION}`));
8879
- console.log("Welcome! Let's get started deploying your infrastructure.\n");
8880
- const service = await clack14.select({
8881
- message: "Which service would you like to set up?",
8882
- options: [
8883
- {
8884
- value: "email",
8885
- label: "Email",
8886
- hint: "AWS SES email infrastructure"
8887
- },
8888
- {
8889
- value: "sms",
8890
- label: "SMS",
8891
- hint: "Coming soon - AWS End User Messaging"
8892
- }
8893
- ]
8894
- });
8895
- if (clack14.isCancel(service)) {
8896
- clack14.cancel("Operation cancelled.");
8897
- process.exit(0);
8898
- }
8899
- if (service === "sms") {
8900
- clack14.log.warn("SMS infrastructure is coming soon!");
8901
- console.log(
8902
- `
8903
- Check back soon or follow our progress at ${pc15.cyan("https://github.com/wraps-team/wraps")}
8904
- `
8905
- );
8906
- process.exit(0);
8907
- }
8908
- const action = await clack14.select({
9265
+ clack16.intro(pc17.bold(`WRAPS CLI v${VERSION}`));
9266
+ console.log("Welcome! Let's get started deploying your email infrastructure.\n");
9267
+ const action = await clack16.select({
8909
9268
  message: "What would you like to do?",
8910
9269
  options: [
8911
9270
  {
@@ -8920,8 +9279,8 @@ Check back soon or follow our progress at ${pc15.cyan("https://github.com/wraps-
8920
9279
  }
8921
9280
  ]
8922
9281
  });
8923
- if (clack14.isCancel(action)) {
8924
- clack14.cancel("Operation cancelled.");
9282
+ if (clack16.isCancel(action)) {
9283
+ clack16.cancel("Operation cancelled.");
8925
9284
  process.exit(0);
8926
9285
  }
8927
9286
  if (action === "init") {
@@ -8950,20 +9309,20 @@ async function run() {
8950
9309
  const telemetry = getTelemetryClient();
8951
9310
  if (telemetry.shouldShowNotification()) {
8952
9311
  console.log();
8953
- clack14.log.info(pc15.bold("Anonymous Telemetry"));
9312
+ clack16.log.info(pc17.bold("Anonymous Telemetry"));
8954
9313
  console.log(
8955
- ` Wraps collects ${pc15.cyan("anonymous usage data")} to improve the CLI.`
9314
+ ` Wraps collects ${pc17.cyan("anonymous usage data")} to improve the CLI.`
8956
9315
  );
8957
9316
  console.log(
8958
- ` We ${pc15.bold("never")} collect: domains, AWS credentials, email content, or PII.`
9317
+ ` We ${pc17.bold("never")} collect: domains, AWS credentials, email content, or PII.`
8959
9318
  );
8960
9319
  console.log(
8961
- ` We ${pc15.bold("only")} collect: command names, success/failure, CLI version, OS.`
9320
+ ` We ${pc17.bold("only")} collect: command names, success/failure, CLI version, OS.`
8962
9321
  );
8963
9322
  console.log();
8964
- console.log(` Opt-out anytime: ${pc15.cyan("wraps telemetry disable")}`);
8965
- console.log(` Or set: ${pc15.cyan("WRAPS_TELEMETRY_DISABLED=1")}`);
8966
- console.log(` Learn more: ${pc15.cyan("https://wraps.dev/docs/telemetry")}`);
9323
+ console.log(` Opt-out anytime: ${pc17.cyan("wraps telemetry disable")}`);
9324
+ console.log(` Or set: ${pc17.cyan("WRAPS_TELEMETRY_DISABLED=1")}`);
9325
+ console.log(` Learn more: ${pc17.cyan("https://wraps.dev/docs/telemetry")}`);
8967
9326
  console.log();
8968
9327
  telemetry.markNotificationShown();
8969
9328
  }
@@ -9010,15 +9369,33 @@ async function run() {
9010
9369
  preview: flags.preview
9011
9370
  });
9012
9371
  break;
9372
+ case "status":
9373
+ await emailStatus({
9374
+ account: flags.account
9375
+ });
9376
+ break;
9377
+ case "verify": {
9378
+ if (!flags.domain) {
9379
+ clack16.log.error("--domain flag is required");
9380
+ console.log(
9381
+ `
9382
+ Usage: ${pc17.cyan("wraps email verify --domain yourapp.com")}
9383
+ `
9384
+ );
9385
+ process.exit(1);
9386
+ }
9387
+ await verifyDomain({ domain: flags.domain });
9388
+ break;
9389
+ }
9013
9390
  case "domains": {
9014
9391
  const domainsSubCommand = args.sub[2];
9015
9392
  switch (domainsSubCommand) {
9016
9393
  case "add": {
9017
9394
  if (!flags.domain) {
9018
- clack14.log.error("--domain flag is required");
9395
+ clack16.log.error("--domain flag is required");
9019
9396
  console.log(
9020
9397
  `
9021
- Usage: ${pc15.cyan("wraps email domains add --domain yourapp.com")}
9398
+ Usage: ${pc17.cyan("wraps email domains add --domain yourapp.com")}
9022
9399
  `
9023
9400
  );
9024
9401
  process.exit(1);
@@ -9031,10 +9408,10 @@ Usage: ${pc15.cyan("wraps email domains add --domain yourapp.com")}
9031
9408
  break;
9032
9409
  case "verify": {
9033
9410
  if (!flags.domain) {
9034
- clack14.log.error("--domain flag is required");
9411
+ clack16.log.error("--domain flag is required");
9035
9412
  console.log(
9036
9413
  `
9037
- Usage: ${pc15.cyan("wraps email domains verify --domain yourapp.com")}
9414
+ Usage: ${pc17.cyan("wraps email domains verify --domain yourapp.com")}
9038
9415
  `
9039
9416
  );
9040
9417
  process.exit(1);
@@ -9044,10 +9421,10 @@ Usage: ${pc15.cyan("wraps email domains verify --domain yourapp.com")}
9044
9421
  }
9045
9422
  case "get-dkim": {
9046
9423
  if (!flags.domain) {
9047
- clack14.log.error("--domain flag is required");
9424
+ clack16.log.error("--domain flag is required");
9048
9425
  console.log(
9049
9426
  `
9050
- Usage: ${pc15.cyan("wraps email domains get-dkim --domain yourapp.com")}
9427
+ Usage: ${pc17.cyan("wraps email domains get-dkim --domain yourapp.com")}
9051
9428
  `
9052
9429
  );
9053
9430
  process.exit(1);
@@ -9057,10 +9434,10 @@ Usage: ${pc15.cyan("wraps email domains get-dkim --domain yourapp.com")}
9057
9434
  }
9058
9435
  case "remove": {
9059
9436
  if (!flags.domain) {
9060
- clack14.log.error("--domain flag is required");
9437
+ clack16.log.error("--domain flag is required");
9061
9438
  console.log(
9062
9439
  `
9063
- Usage: ${pc15.cyan("wraps email domains remove --domain yourapp.com --force")}
9440
+ Usage: ${pc17.cyan("wraps email domains remove --domain yourapp.com --force")}
9064
9441
  `
9065
9442
  );
9066
9443
  process.exit(1);
@@ -9072,23 +9449,29 @@ Usage: ${pc15.cyan("wraps email domains remove --domain yourapp.com --force")}
9072
9449
  break;
9073
9450
  }
9074
9451
  default:
9075
- clack14.log.error(
9452
+ clack16.log.error(
9076
9453
  `Unknown domains command: ${domainsSubCommand || "(none)"}`
9077
9454
  );
9078
9455
  console.log(
9079
9456
  `
9080
- Available commands: ${pc15.cyan("add")}, ${pc15.cyan("list")}, ${pc15.cyan("verify")}, ${pc15.cyan("get-dkim")}, ${pc15.cyan("remove")}
9457
+ Available commands: ${pc17.cyan("add")}, ${pc17.cyan("list")}, ${pc17.cyan("verify")}, ${pc17.cyan("get-dkim")}, ${pc17.cyan("remove")}
9081
9458
  `
9082
9459
  );
9083
9460
  process.exit(1);
9084
9461
  }
9085
9462
  break;
9086
9463
  }
9464
+ case "destroy":
9465
+ await emailDestroy({
9466
+ force: flags.force,
9467
+ preview: flags.preview
9468
+ });
9469
+ break;
9087
9470
  default:
9088
- clack14.log.error(`Unknown email command: ${subCommand}`);
9471
+ clack16.log.error(`Unknown email command: ${subCommand}`);
9089
9472
  console.log(
9090
9473
  `
9091
- Run ${pc15.cyan("wraps --help")} for available commands.
9474
+ Run ${pc17.cyan("wraps --help")} for available commands.
9092
9475
  `
9093
9476
  );
9094
9477
  process.exit(1);
@@ -9111,11 +9494,11 @@ Run ${pc15.cyan("wraps --help")} for available commands.
9111
9494
  });
9112
9495
  break;
9113
9496
  default:
9114
- clack14.log.error(`Unknown dashboard command: ${subCommand}`);
9497
+ clack16.log.error(`Unknown dashboard command: ${subCommand}`);
9115
9498
  console.log(`
9116
- Available commands: ${pc15.cyan("update-role")}
9499
+ Available commands: ${pc17.cyan("update-role")}
9117
9500
  `);
9118
- console.log(`Run ${pc15.cyan("wraps --help")} for more information.
9501
+ console.log(`Run ${pc17.cyan("wraps --help")} for more information.
9119
9502
  `);
9120
9503
  process.exit(1);
9121
9504
  }
@@ -9127,15 +9510,6 @@ Available commands: ${pc15.cyan("update-role")}
9127
9510
  });
9128
9511
  return;
9129
9512
  }
9130
- if (primaryCommand === "sms" && subCommand) {
9131
- clack14.log.warn("SMS infrastructure is coming soon!");
9132
- console.log(
9133
- `
9134
- Check back soon or follow our progress at ${pc15.cyan("https://github.com/wraps-team/wraps")}
9135
- `
9136
- );
9137
- process.exit(0);
9138
- }
9139
9513
  switch (primaryCommand) {
9140
9514
  // Global commands (work across all services)
9141
9515
  case "status":
@@ -9151,8 +9525,8 @@ Check back soon or follow our progress at ${pc15.cyan("https://github.com/wraps-
9151
9525
  break;
9152
9526
  case "dashboard":
9153
9527
  if (!subCommand) {
9154
- clack14.log.warn(
9155
- `'wraps dashboard' is deprecated. Use ${pc15.cyan("wraps console")} instead.`
9528
+ clack16.log.warn(
9529
+ `'wraps dashboard' is deprecated. Use ${pc17.cyan("wraps console")} instead.`
9156
9530
  );
9157
9531
  await dashboard({
9158
9532
  port: flags.port,
@@ -9182,10 +9556,10 @@ Check back soon or follow our progress at ${pc15.cyan("https://github.com/wraps-
9182
9556
  await telemetryStatus();
9183
9557
  break;
9184
9558
  default:
9185
- clack14.log.error(`Unknown telemetry command: ${subCommand}`);
9559
+ clack16.log.error(`Unknown telemetry command: ${subCommand}`);
9186
9560
  console.log(
9187
9561
  `
9188
- Available commands: ${pc15.cyan("enable")}, ${pc15.cyan("disable")}, ${pc15.cyan("status")}
9562
+ Available commands: ${pc17.cyan("enable")}, ${pc17.cyan("disable")}, ${pc17.cyan("status")}
9189
9563
  `
9190
9564
  );
9191
9565
  process.exit(1);
@@ -9194,7 +9568,6 @@ Available commands: ${pc15.cyan("enable")}, ${pc15.cyan("disable")}, ${pc15.cyan
9194
9568
  }
9195
9569
  // Show help for service without subcommand
9196
9570
  case "email":
9197
- case "sms":
9198
9571
  console.log(
9199
9572
  `
9200
9573
  Please specify a command for ${primaryCommand} service.
@@ -9203,10 +9576,10 @@ Please specify a command for ${primaryCommand} service.
9203
9576
  showHelp();
9204
9577
  break;
9205
9578
  default:
9206
- clack14.log.error(`Unknown command: ${primaryCommand}`);
9579
+ clack16.log.error(`Unknown command: ${primaryCommand}`);
9207
9580
  console.log(
9208
9581
  `
9209
- Run ${pc15.cyan("wraps --help")} for available commands.
9582
+ Run ${pc17.cyan("wraps --help")} for available commands.
9210
9583
  `
9211
9584
  );
9212
9585
  process.exit(1);