@wraps.dev/cli 0.1.2 → 0.1.4

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
@@ -9,15 +9,214 @@ var __export = (target, all3) => {
9
9
  __defProp(target, name, { get: all3[name], enumerable: true });
10
10
  };
11
11
 
12
- // ../../node_modules/.pnpm/tsup@8.5.0_jiti@2.6.1_postcss@8.5.6_typescript@5.8.3/node_modules/tsup/assets/esm_shims.js
12
+ // ../../node_modules/.pnpm/tsup@8.5.0_jiti@2.6.1_postcss@8.5.6_tsx@4.20.6_typescript@5.8.3/node_modules/tsup/assets/esm_shims.js
13
13
  import path from "path";
14
14
  import { fileURLToPath } from "url";
15
15
  var init_esm_shims = __esm({
16
- "../../node_modules/.pnpm/tsup@8.5.0_jiti@2.6.1_postcss@8.5.6_typescript@5.8.3/node_modules/tsup/assets/esm_shims.js"() {
16
+ "../../node_modules/.pnpm/tsup@8.5.0_jiti@2.6.1_postcss@8.5.6_tsx@4.20.6_typescript@5.8.3/node_modules/tsup/assets/esm_shims.js"() {
17
17
  "use strict";
18
18
  }
19
19
  });
20
20
 
21
+ // src/utils/errors.ts
22
+ import * as clack from "@clack/prompts";
23
+ import pc from "picocolors";
24
+ function handleCLIError(error) {
25
+ console.error("");
26
+ if (error instanceof WrapsError) {
27
+ clack.log.error(error.message);
28
+ if (error.suggestion) {
29
+ console.log(`
30
+ ${pc.yellow("Suggestion:")}`);
31
+ console.log(` ${pc.white(error.suggestion)}
32
+ `);
33
+ }
34
+ if (error.docsUrl) {
35
+ console.log(`${pc.dim("Documentation:")}`);
36
+ console.log(` ${pc.blue(error.docsUrl)}
37
+ `);
38
+ }
39
+ process.exit(1);
40
+ }
41
+ clack.log.error("An unexpected error occurred");
42
+ console.error(error);
43
+ console.log(`
44
+ ${pc.dim("If this persists, please report at:")}`);
45
+ console.log(` ${pc.blue("https://github.com/wraps-team/wraps/issues")}
46
+ `);
47
+ process.exit(1);
48
+ }
49
+ var WrapsError, errors;
50
+ var init_errors = __esm({
51
+ "src/utils/errors.ts"() {
52
+ "use strict";
53
+ init_esm_shims();
54
+ WrapsError = class extends Error {
55
+ constructor(message, code, suggestion, docsUrl) {
56
+ super(message);
57
+ this.code = code;
58
+ this.suggestion = suggestion;
59
+ this.docsUrl = docsUrl;
60
+ this.name = "WrapsError";
61
+ }
62
+ };
63
+ errors = {
64
+ noAWSCredentials: () => new WrapsError(
65
+ "AWS credentials not found",
66
+ "NO_AWS_CREDENTIALS",
67
+ "Run: aws configure\nOr set AWS_PROFILE environment variable",
68
+ "https://docs.wraps.dev/setup/aws-credentials"
69
+ ),
70
+ stackExists: (stackName) => new WrapsError(
71
+ `Stack "${stackName}" already exists`,
72
+ "STACK_EXISTS",
73
+ `To update: wraps upgrade
74
+ To remove: wraps destroy --stack ${stackName}`,
75
+ "https://docs.wraps.dev/cli/upgrade"
76
+ ),
77
+ invalidRegion: (region) => new WrapsError(
78
+ `Invalid AWS region: ${region}`,
79
+ "INVALID_REGION",
80
+ "Use a valid AWS region like: us-east-1, eu-west-1, ap-southeast-1",
81
+ "https://docs.aws.amazon.com/general/latest/gr/rande.html"
82
+ ),
83
+ pulumiError: (message) => new WrapsError(
84
+ `Infrastructure deployment failed: ${message}`,
85
+ "PULUMI_ERROR",
86
+ "Check your AWS permissions and try again",
87
+ "https://docs.wraps.dev/troubleshooting"
88
+ ),
89
+ noStack: () => new WrapsError(
90
+ "No Wraps infrastructure found in this AWS account",
91
+ "NO_STACK",
92
+ "Run: wraps init\nTo deploy new infrastructure",
93
+ "https://docs.wraps.dev/cli/init"
94
+ ),
95
+ pulumiNotInstalled: () => new WrapsError(
96
+ "Pulumi CLI is not installed",
97
+ "PULUMI_NOT_INSTALLED",
98
+ "Install Pulumi:\n macOS: brew install pulumi/tap/pulumi\n Linux: curl -fsSL https://get.pulumi.com | sh\n Windows: choco install pulumi\n\nOr download from: https://www.pulumi.com/docs/install/",
99
+ "https://www.pulumi.com/docs/install/"
100
+ )
101
+ };
102
+ }
103
+ });
104
+
105
+ // src/utils/aws.ts
106
+ var aws_exports = {};
107
+ __export(aws_exports, {
108
+ checkRegion: () => checkRegion,
109
+ getAWSRegion: () => getAWSRegion,
110
+ isSESSandbox: () => isSESSandbox,
111
+ listSESDomains: () => listSESDomains,
112
+ validateAWSCredentials: () => validateAWSCredentials
113
+ });
114
+ import {
115
+ GetIdentityVerificationAttributesCommand,
116
+ ListIdentitiesCommand,
117
+ SESClient
118
+ } from "@aws-sdk/client-ses";
119
+ import { GetCallerIdentityCommand, STSClient } from "@aws-sdk/client-sts";
120
+ async function validateAWSCredentials() {
121
+ const sts = new STSClient({ region: "us-east-1" });
122
+ try {
123
+ const identity = await sts.send(new GetCallerIdentityCommand({}));
124
+ return {
125
+ accountId: identity.Account,
126
+ userId: identity.UserId,
127
+ arn: identity.Arn
128
+ };
129
+ } catch (_error) {
130
+ throw errors.noAWSCredentials();
131
+ }
132
+ }
133
+ async function checkRegion(region) {
134
+ const validRegions = [
135
+ "us-east-1",
136
+ "us-east-2",
137
+ "us-west-1",
138
+ "us-west-2",
139
+ "af-south-1",
140
+ "ap-east-1",
141
+ "ap-south-1",
142
+ "ap-northeast-1",
143
+ "ap-northeast-2",
144
+ "ap-northeast-3",
145
+ "ap-southeast-1",
146
+ "ap-southeast-2",
147
+ "ap-southeast-3",
148
+ "ca-central-1",
149
+ "eu-central-1",
150
+ "eu-west-1",
151
+ "eu-west-2",
152
+ "eu-west-3",
153
+ "eu-south-1",
154
+ "eu-north-1",
155
+ "me-south-1",
156
+ "sa-east-1"
157
+ ];
158
+ return validRegions.includes(region);
159
+ }
160
+ async function getAWSRegion() {
161
+ if (process.env.AWS_REGION) {
162
+ return process.env.AWS_REGION;
163
+ }
164
+ if (process.env.AWS_DEFAULT_REGION) {
165
+ return process.env.AWS_DEFAULT_REGION;
166
+ }
167
+ return "us-east-1";
168
+ }
169
+ async function listSESDomains(region) {
170
+ const ses = new SESClient({ region });
171
+ try {
172
+ const identitiesResponse = await ses.send(
173
+ new ListIdentitiesCommand({
174
+ IdentityType: "Domain"
175
+ })
176
+ );
177
+ const identities = identitiesResponse.Identities || [];
178
+ if (identities.length === 0) {
179
+ return [];
180
+ }
181
+ const attributesResponse = await ses.send(
182
+ new GetIdentityVerificationAttributesCommand({
183
+ Identities: identities
184
+ })
185
+ );
186
+ const attributes = attributesResponse.VerificationAttributes || {};
187
+ return identities.map((domain) => ({
188
+ domain,
189
+ verified: attributes[domain]?.VerificationStatus === "Success"
190
+ }));
191
+ } catch (error) {
192
+ console.error("Error listing SES domains:", error);
193
+ return [];
194
+ }
195
+ }
196
+ async function isSESSandbox(region) {
197
+ const ses = new SESClient({ region });
198
+ try {
199
+ await ses.send(
200
+ new ListIdentitiesCommand({
201
+ MaxItems: 1
202
+ })
203
+ );
204
+ return false;
205
+ } catch (error) {
206
+ if (error.name === "InvalidParameterValue") {
207
+ return true;
208
+ }
209
+ throw error;
210
+ }
211
+ }
212
+ var init_aws = __esm({
213
+ "src/utils/aws.ts"() {
214
+ "use strict";
215
+ init_esm_shims();
216
+ init_errors();
217
+ }
218
+ });
219
+
21
220
  // src/utils/costs.ts
22
221
  function estimateStorageSize(emailsPerMonth, retention, numEventTypes = 8) {
23
222
  const avgRecordSizeKB = 2;
@@ -78,17 +277,16 @@ function calculateDynamoDBCost(config, emailsPerMonth) {
78
277
  const storageCost = Math.max(0, storageGB - FREE_TIER.DYNAMODB_STORAGE_GB) * AWS_PRICING.DYNAMODB_STORAGE_PER_GB;
79
278
  return {
80
279
  monthly: writeCost + storageCost,
81
- description: `Email history (${retention}, ~${storageGB.toFixed(1)} GB, ${numEventTypes} event types)`
280
+ description: `Email history (${retention}, ~${storageGB.toFixed(2)} GB at steady-state, ${numEventTypes} event types)`
82
281
  };
83
282
  }
84
283
  function calculateTrackingCost(config) {
85
284
  if (!config.tracking?.enabled) {
86
285
  return;
87
286
  }
88
- const cost = config.tracking.customRedirectDomain ? 0.5 : 0;
89
287
  return {
90
- monthly: cost,
91
- description: config.tracking.customRedirectDomain ? "Open/click tracking with custom domain (Route53 hosted zone)" : "Open/click tracking (no additional cost)"
288
+ monthly: 0,
289
+ description: "Open/click tracking (no additional cost)"
92
290
  };
93
291
  }
94
292
  function calculateReputationMetricsCost(config) {
@@ -1334,7 +1532,10 @@ async function createIAMRole(config) {
1334
1532
  "dynamodb:Scan",
1335
1533
  "dynamodb:BatchGetItem"
1336
1534
  ],
1337
- Resource: "arn:aws:dynamodb:*:*:table/wraps-email-*"
1535
+ Resource: [
1536
+ "arn:aws:dynamodb:*:*:table/wraps-email-*",
1537
+ "arn:aws:dynamodb:*:*:table/wraps-email-*/index/*"
1538
+ ]
1338
1539
  });
1339
1540
  }
1340
1541
  if (config.emailConfig.eventTracking?.enabled) {
@@ -1387,14 +1588,33 @@ function getPackageRoot() {
1387
1588
  }
1388
1589
  throw new Error("Could not find package.json");
1389
1590
  }
1390
- async function bundleLambda(functionPath) {
1591
+ async function getLambdaCode(functionName) {
1592
+ const packageRoot = getPackageRoot();
1593
+ const distLambdaPath = join(packageRoot, "dist", "lambda", functionName);
1594
+ const distBundleMarker = join(distLambdaPath, ".bundled");
1595
+ if (existsSync(distBundleMarker)) {
1596
+ return distLambdaPath;
1597
+ }
1598
+ const lambdaPath = join(packageRoot, "lambda", functionName);
1599
+ const lambdaBundleMarker = join(lambdaPath, ".bundled");
1600
+ if (existsSync(lambdaBundleMarker)) {
1601
+ return lambdaPath;
1602
+ }
1603
+ const sourcePath = join(lambdaPath, "index.ts");
1604
+ if (!existsSync(sourcePath)) {
1605
+ throw new Error(
1606
+ `Lambda source not found: ${sourcePath}
1607
+ This usually means the build process didn't complete successfully.
1608
+ Try running: pnpm build`
1609
+ );
1610
+ }
1391
1611
  const buildId = randomBytes(8).toString("hex");
1392
1612
  const outdir = join(tmpdir(), `wraps-lambda-${buildId}`);
1393
1613
  if (!existsSync(outdir)) {
1394
1614
  mkdirSync(outdir, { recursive: true });
1395
1615
  }
1396
1616
  await build({
1397
- entryPoints: [functionPath],
1617
+ entryPoints: [sourcePath],
1398
1618
  bundle: true,
1399
1619
  platform: "node",
1400
1620
  target: "node20",
@@ -1408,10 +1628,7 @@ async function bundleLambda(functionPath) {
1408
1628
  return outdir;
1409
1629
  }
1410
1630
  async function deployLambdaFunctions(config) {
1411
- const packageRoot = getPackageRoot();
1412
- const lambdaDir = join(packageRoot, "lambda");
1413
- const eventProcessorPath = join(lambdaDir, "event-processor", "index.ts");
1414
- const eventProcessorBundle = await bundleLambda(eventProcessorPath);
1631
+ const eventProcessorCode = await getLambdaCode("event-processor");
1415
1632
  const lambdaRole = new aws4.iam.Role("wraps-email-lambda-role", {
1416
1633
  assumeRolePolicy: JSON.stringify({
1417
1634
  Version: "2012-10-17",
@@ -1473,7 +1690,7 @@ async function deployLambdaFunctions(config) {
1473
1690
  runtime: aws4.lambda.Runtime.NodeJS20dX,
1474
1691
  handler: "index.handler",
1475
1692
  role: lambdaRole.arn,
1476
- code: new pulumi3.asset.FileArchive(eventProcessorBundle),
1693
+ code: new pulumi3.asset.FileArchive(eventProcessorCode),
1477
1694
  timeout: 300,
1478
1695
  // 5 minutes (matches SQS visibility timeout)
1479
1696
  memorySize: 512,
@@ -1714,143 +1931,8 @@ async function deployEmailStack(config) {
1714
1931
  };
1715
1932
  }
1716
1933
 
1717
- // src/utils/aws.ts
1718
- init_esm_shims();
1719
- import {
1720
- GetIdentityVerificationAttributesCommand,
1721
- ListIdentitiesCommand,
1722
- SESClient
1723
- } from "@aws-sdk/client-ses";
1724
- import { GetCallerIdentityCommand, STSClient } from "@aws-sdk/client-sts";
1725
-
1726
- // src/utils/errors.ts
1727
- init_esm_shims();
1728
- import * as clack from "@clack/prompts";
1729
- import pc from "picocolors";
1730
- var WrapsError = class extends Error {
1731
- constructor(message, code, suggestion, docsUrl) {
1732
- super(message);
1733
- this.code = code;
1734
- this.suggestion = suggestion;
1735
- this.docsUrl = docsUrl;
1736
- this.name = "WrapsError";
1737
- }
1738
- };
1739
- function handleCLIError(error) {
1740
- console.error("");
1741
- if (error instanceof WrapsError) {
1742
- clack.log.error(error.message);
1743
- if (error.suggestion) {
1744
- console.log(`
1745
- ${pc.yellow("Suggestion:")}`);
1746
- console.log(` ${pc.white(error.suggestion)}
1747
- `);
1748
- }
1749
- if (error.docsUrl) {
1750
- console.log(`${pc.dim("Documentation:")}`);
1751
- console.log(` ${pc.blue(error.docsUrl)}
1752
- `);
1753
- }
1754
- process.exit(1);
1755
- }
1756
- clack.log.error("An unexpected error occurred");
1757
- console.error(error);
1758
- console.log(`
1759
- ${pc.dim("If this persists, please report at:")}`);
1760
- console.log(` ${pc.blue("https://github.com/wraps-team/wraps/issues")}
1761
- `);
1762
- process.exit(1);
1763
- }
1764
- var errors = {
1765
- noAWSCredentials: () => new WrapsError(
1766
- "AWS credentials not found",
1767
- "NO_AWS_CREDENTIALS",
1768
- "Run: aws configure\nOr set AWS_PROFILE environment variable",
1769
- "https://docs.wraps.dev/setup/aws-credentials"
1770
- ),
1771
- stackExists: (stackName) => new WrapsError(
1772
- `Stack "${stackName}" already exists`,
1773
- "STACK_EXISTS",
1774
- `To update: wraps upgrade
1775
- To remove: wraps destroy --stack ${stackName}`,
1776
- "https://docs.wraps.dev/cli/upgrade"
1777
- ),
1778
- invalidRegion: (region) => new WrapsError(
1779
- `Invalid AWS region: ${region}`,
1780
- "INVALID_REGION",
1781
- "Use a valid AWS region like: us-east-1, eu-west-1, ap-southeast-1",
1782
- "https://docs.aws.amazon.com/general/latest/gr/rande.html"
1783
- ),
1784
- pulumiError: (message) => new WrapsError(
1785
- `Infrastructure deployment failed: ${message}`,
1786
- "PULUMI_ERROR",
1787
- "Check your AWS permissions and try again",
1788
- "https://docs.wraps.dev/troubleshooting"
1789
- ),
1790
- noStack: () => new WrapsError(
1791
- "No Wraps infrastructure found in this AWS account",
1792
- "NO_STACK",
1793
- "Run: wraps init\nTo deploy new infrastructure",
1794
- "https://docs.wraps.dev/cli/init"
1795
- ),
1796
- pulumiNotInstalled: () => new WrapsError(
1797
- "Pulumi CLI is not installed",
1798
- "PULUMI_NOT_INSTALLED",
1799
- "Install Pulumi:\n macOS: brew install pulumi/tap/pulumi\n Linux: curl -fsSL https://get.pulumi.com | sh\n Windows: choco install pulumi\n\nOr download from: https://www.pulumi.com/docs/install/",
1800
- "https://www.pulumi.com/docs/install/"
1801
- )
1802
- };
1803
-
1804
- // src/utils/aws.ts
1805
- async function validateAWSCredentials() {
1806
- const sts = new STSClient({ region: "us-east-1" });
1807
- try {
1808
- const identity = await sts.send(new GetCallerIdentityCommand({}));
1809
- return {
1810
- accountId: identity.Account,
1811
- userId: identity.UserId,
1812
- arn: identity.Arn
1813
- };
1814
- } catch (_error) {
1815
- throw errors.noAWSCredentials();
1816
- }
1817
- }
1818
- async function getAWSRegion() {
1819
- if (process.env.AWS_REGION) {
1820
- return process.env.AWS_REGION;
1821
- }
1822
- if (process.env.AWS_DEFAULT_REGION) {
1823
- return process.env.AWS_DEFAULT_REGION;
1824
- }
1825
- return "us-east-1";
1826
- }
1827
- async function listSESDomains(region) {
1828
- const ses = new SESClient({ region });
1829
- try {
1830
- const identitiesResponse = await ses.send(
1831
- new ListIdentitiesCommand({
1832
- IdentityType: "Domain"
1833
- })
1834
- );
1835
- const identities = identitiesResponse.Identities || [];
1836
- if (identities.length === 0) {
1837
- return [];
1838
- }
1839
- const attributesResponse = await ses.send(
1840
- new GetIdentityVerificationAttributesCommand({
1841
- Identities: identities
1842
- })
1843
- );
1844
- const attributes = attributesResponse.VerificationAttributes || {};
1845
- return identities.map((domain) => ({
1846
- domain,
1847
- verified: attributes[domain]?.VerificationStatus === "Success"
1848
- }));
1849
- } catch (error) {
1850
- console.error("Error listing SES domains:", error);
1851
- return [];
1852
- }
1853
- }
1934
+ // src/commands/connect.ts
1935
+ init_aws();
1854
1936
 
1855
1937
  // src/utils/fs.ts
1856
1938
  init_esm_shims();
@@ -2219,6 +2301,7 @@ init_prompts();
2219
2301
 
2220
2302
  // src/utils/pulumi.ts
2221
2303
  init_esm_shims();
2304
+ init_errors();
2222
2305
  import { exec } from "child_process";
2223
2306
  import { promisify } from "util";
2224
2307
  import * as automation2 from "@pulumi/pulumi/automation/index.js";
@@ -3787,6 +3870,7 @@ async function startConsoleServer(config) {
3787
3870
  }
3788
3871
 
3789
3872
  // src/commands/console.ts
3873
+ init_aws();
3790
3874
  async function runConsole(options) {
3791
3875
  clack5.intro(pc5.bold("Wraps Console"));
3792
3876
  const progress = new DeploymentProgress();
@@ -3838,6 +3922,7 @@ async function runConsole(options) {
3838
3922
 
3839
3923
  // src/commands/destroy.ts
3840
3924
  init_esm_shims();
3925
+ init_aws();
3841
3926
  import * as clack6 from "@clack/prompts";
3842
3927
  import * as pulumi7 from "@pulumi/pulumi";
3843
3928
  import pc6 from "picocolors";
@@ -3906,6 +3991,7 @@ init_esm_shims();
3906
3991
  import * as clack7 from "@clack/prompts";
3907
3992
  import * as pulumi8 from "@pulumi/pulumi";
3908
3993
  import pc7 from "picocolors";
3994
+ init_aws();
3909
3995
  init_costs();
3910
3996
  init_presets();
3911
3997
  init_prompts();
@@ -4112,6 +4198,7 @@ async function init(options) {
4112
4198
 
4113
4199
  // src/commands/restore.ts
4114
4200
  init_esm_shims();
4201
+ init_aws();
4115
4202
  import * as clack8 from "@clack/prompts";
4116
4203
  import * as pulumi9 from "@pulumi/pulumi";
4117
4204
  import pc8 from "picocolors";
@@ -4216,6 +4303,7 @@ ${pc8.green("\u2713")} ${pc8.bold("Infrastructure removed successfully!")}
4216
4303
 
4217
4304
  // src/commands/status.ts
4218
4305
  init_esm_shims();
4306
+ init_aws();
4219
4307
  import * as clack9 from "@clack/prompts";
4220
4308
  import * as pulumi10 from "@pulumi/pulumi";
4221
4309
  import pc9 from "picocolors";
@@ -4286,6 +4374,7 @@ init_esm_shims();
4286
4374
  import * as clack10 from "@clack/prompts";
4287
4375
  import * as pulumi11 from "@pulumi/pulumi";
4288
4376
  import pc10 from "picocolors";
4377
+ init_aws();
4289
4378
  init_costs();
4290
4379
  init_presets();
4291
4380
  init_prompts();
@@ -4435,6 +4524,36 @@ ${pc10.bold("Current Configuration:")}
4435
4524
  break;
4436
4525
  }
4437
4526
  case "tracking-domain": {
4527
+ if (!config.domain) {
4528
+ clack10.log.error(
4529
+ "No sending domain configured. You must configure a sending domain before adding a custom tracking domain."
4530
+ );
4531
+ clack10.log.info(
4532
+ `Use ${pc10.cyan("wraps init")} to set up a sending domain first.`
4533
+ );
4534
+ process.exit(1);
4535
+ }
4536
+ const { listSESDomains: listSESDomains2 } = await Promise.resolve().then(() => (init_aws(), aws_exports));
4537
+ const domains = await progress.execute(
4538
+ "Checking domain verification status",
4539
+ async () => await listSESDomains2(region)
4540
+ );
4541
+ const sendingDomain = domains.find((d) => d.domain === config.domain);
4542
+ if (!sendingDomain?.verified) {
4543
+ clack10.log.error(
4544
+ `Sending domain ${pc10.cyan(config.domain)} is not verified.`
4545
+ );
4546
+ clack10.log.info(
4547
+ "You must verify your sending domain before adding a custom tracking domain."
4548
+ );
4549
+ clack10.log.info(
4550
+ `Use ${pc10.cyan("wraps verify")} to check DNS records and complete verification.`
4551
+ );
4552
+ process.exit(1);
4553
+ }
4554
+ progress.info(
4555
+ `Sending domain ${pc10.cyan(config.domain)} is verified ${pc10.green("\u2713")}`
4556
+ );
4438
4557
  const trackingDomain = await clack10.text({
4439
4558
  message: "Custom tracking redirect domain:",
4440
4559
  placeholder: "track.yourdomain.com",
@@ -4726,6 +4845,7 @@ ${pc10.green("\u2713")} ${pc10.bold("Upgrade complete!")}
4726
4845
 
4727
4846
  // src/commands/verify.ts
4728
4847
  init_esm_shims();
4848
+ init_aws();
4729
4849
  import { Resolver } from "dns/promises";
4730
4850
  import { GetEmailIdentityCommand as GetEmailIdentityCommand3, SESv2Client as SESv2Client3 } from "@aws-sdk/client-sesv2";
4731
4851
  import * as clack11 from "@clack/prompts";
@@ -4894,6 +5014,7 @@ function printCompletionScript() {
4894
5014
  }
4895
5015
 
4896
5016
  // src/cli.ts
5017
+ init_errors();
4897
5018
  setupTabCompletion();
4898
5019
  function showHelp() {
4899
5020
  clack12.intro(pc12.bold("WRAPS CLI"));