@wraps.dev/cli 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -4,6 +4,9 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
4
4
  var __esm = (fn, res) => function __init() {
5
5
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
6
  };
7
+ var __commonJS = (cb, mod) => function __require() {
8
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
9
+ };
7
10
  var __export = (target, all3) => {
8
11
  for (var name in all3)
9
12
  __defProp(target, name, { get: all3[name], enumerable: true });
@@ -18,12 +21,456 @@ var init_esm_shims = __esm({
18
21
  }
19
22
  });
20
23
 
24
+ // src/utils/shared/ci-detection.ts
25
+ function isCI() {
26
+ if (process.env.CI === "true" || process.env.CI === "1") {
27
+ return true;
28
+ }
29
+ const ciEnvVars = [
30
+ "GITHUB_ACTIONS",
31
+ // GitHub Actions
32
+ "GITLAB_CI",
33
+ // GitLab CI
34
+ "CIRCLECI",
35
+ // CircleCI
36
+ "TRAVIS",
37
+ // Travis CI
38
+ "JENKINS_URL",
39
+ // Jenkins
40
+ "BUILDKITE",
41
+ // Buildkite
42
+ "DRONE",
43
+ // Drone
44
+ "SEMAPHORE",
45
+ // Semaphore
46
+ "TEAMCITY_VERSION",
47
+ // TeamCity
48
+ "TF_BUILD",
49
+ // Azure Pipelines
50
+ "CODEBUILD_BUILD_ID",
51
+ // AWS CodeBuild
52
+ "NETLIFY",
53
+ // Netlify
54
+ "VERCEL",
55
+ // Vercel
56
+ "HEROKU_TEST_RUN_ID",
57
+ // Heroku CI
58
+ "BUDDY",
59
+ // Buddy
60
+ "BITBUCKET_BUILD_NUMBER"
61
+ // Bitbucket Pipelines
62
+ ];
63
+ return ciEnvVars.some((envVar) => process.env[envVar] !== void 0);
64
+ }
65
+ var init_ci_detection = __esm({
66
+ "src/utils/shared/ci-detection.ts"() {
67
+ "use strict";
68
+ init_esm_shims();
69
+ }
70
+ });
71
+
72
+ // src/telemetry/config.ts
73
+ import Conf from "conf";
74
+ import { v4 as uuidv4 } from "uuid";
75
+ var CONFIG_DEFAULTS, TelemetryConfigManager;
76
+ var init_config = __esm({
77
+ "src/telemetry/config.ts"() {
78
+ "use strict";
79
+ init_esm_shims();
80
+ CONFIG_DEFAULTS = {
81
+ enabled: true,
82
+ anonymousId: uuidv4(),
83
+ notificationShown: false
84
+ };
85
+ TelemetryConfigManager = class {
86
+ config;
87
+ constructor() {
88
+ this.config = new Conf({
89
+ projectName: "wraps",
90
+ configName: "telemetry",
91
+ defaults: CONFIG_DEFAULTS
92
+ });
93
+ }
94
+ /**
95
+ * Check if telemetry is enabled
96
+ */
97
+ isEnabled() {
98
+ return this.config.get("enabled");
99
+ }
100
+ /**
101
+ * Enable or disable telemetry
102
+ */
103
+ setEnabled(enabled) {
104
+ this.config.set("enabled", enabled);
105
+ }
106
+ /**
107
+ * Get the anonymous user ID
108
+ */
109
+ getAnonymousId() {
110
+ return this.config.get("anonymousId");
111
+ }
112
+ /**
113
+ * Check if the first-run notification has been shown
114
+ */
115
+ hasShownNotification() {
116
+ return this.config.get("notificationShown");
117
+ }
118
+ /**
119
+ * Mark the first-run notification as shown
120
+ */
121
+ markNotificationShown() {
122
+ this.config.set("notificationShown", true);
123
+ }
124
+ /**
125
+ * Get the full path to the configuration file
126
+ */
127
+ getConfigPath() {
128
+ return this.config.path;
129
+ }
130
+ /**
131
+ * Reset configuration to defaults
132
+ */
133
+ reset() {
134
+ this.config.clear();
135
+ this.config.set({
136
+ ...CONFIG_DEFAULTS,
137
+ anonymousId: uuidv4()
138
+ });
139
+ }
140
+ };
141
+ }
142
+ });
143
+
144
+ // package.json
145
+ var require_package = __commonJS({
146
+ "package.json"(exports, module) {
147
+ module.exports = {
148
+ name: "@wraps.dev/cli",
149
+ version: "1.4.0",
150
+ description: "CLI for deploying Wraps email infrastructure to your AWS account",
151
+ type: "module",
152
+ main: "./dist/cli.js",
153
+ bin: {
154
+ wraps: "./dist/cli.js"
155
+ },
156
+ files: [
157
+ "dist",
158
+ "README.md",
159
+ "LICENSE"
160
+ ],
161
+ repository: {
162
+ type: "git",
163
+ url: "https://github.com/wraps-team/wraps.git",
164
+ directory: "packages/cli"
165
+ },
166
+ homepage: "https://wraps.dev",
167
+ bugs: {
168
+ url: "https://github.com/wraps-team/wraps/issues"
169
+ },
170
+ publishConfig: {
171
+ access: "public"
172
+ },
173
+ scripts: {
174
+ dev: "tsup --watch",
175
+ build: "pnpm build:console && pnpm build:lambda && tsup",
176
+ "build:lambda": "tsx scripts/build-lambda.ts",
177
+ "build:console": "pnpm --filter @wraps/console build",
178
+ test: "vitest run",
179
+ "test:watch": "vitest --watch",
180
+ "test:ui": "vitest --ui",
181
+ "test:coverage": "vitest run --coverage",
182
+ typecheck: "tsc --noEmit",
183
+ lint: "eslint src",
184
+ prepublishOnly: "pnpm build"
185
+ },
186
+ keywords: [
187
+ "aws",
188
+ "ses",
189
+ "email",
190
+ "infrastructure",
191
+ "cli"
192
+ ],
193
+ author: "Wraps",
194
+ license: "MIT",
195
+ dependencies: {
196
+ "@aws-sdk/client-acm": "3.933.0",
197
+ "@aws-sdk/client-cloudformation": "^3.490.0",
198
+ "@aws-sdk/client-cloudfront": "3.933.0",
199
+ "@aws-sdk/client-cloudwatch": "^3.490.0",
200
+ "@aws-sdk/client-dynamodb": "^3.490.0",
201
+ "@aws-sdk/client-iam": "3.932.0",
202
+ "@aws-sdk/client-lambda": "3.925.0",
203
+ "@aws-sdk/client-mailmanager": "3.925.0",
204
+ "@aws-sdk/client-route-53": "3.925.0",
205
+ "@aws-sdk/client-ses": "^3.490.0",
206
+ "@aws-sdk/client-sesv2": "3.925.0",
207
+ "@aws-sdk/client-sns": "^3.490.0",
208
+ "@aws-sdk/client-sts": "^3.490.0",
209
+ "@aws-sdk/util-dynamodb": "3.927.0",
210
+ "@clack/prompts": "^0.11.0",
211
+ "@pulumi/aws": "^7.11.1",
212
+ "@pulumi/pulumi": "^3.207.0",
213
+ args: "^5.0.3",
214
+ conf: "^13.0.1",
215
+ cosmiconfig: "^9.0.0",
216
+ esbuild: "^0.25.12",
217
+ express: "^4.21.2",
218
+ "get-port": "^7.1.0",
219
+ "http-terminator": "^3.2.0",
220
+ "isomorphic-dompurify": "2.32.0",
221
+ mailparser: "3.9.0",
222
+ open: "^10.1.0",
223
+ picocolors: "^1.1.1",
224
+ tabtab: "^3.0.2",
225
+ uuid: "^11.0.3"
226
+ },
227
+ devDependencies: {
228
+ "@types/args": "5.0.4",
229
+ "@types/express": "^5.0.0",
230
+ "@types/mailparser": "3.4.6",
231
+ "@types/node": "^20.11.0",
232
+ "@types/uuid": "^10.0.0",
233
+ "@vitest/coverage-v8": "4.0.7",
234
+ "aws-sdk-client-mock": "4.1.0",
235
+ "aws-sdk-client-mock-vitest": "7.0.1",
236
+ eslint: "^8.56.0",
237
+ tsup: "^8.0.1",
238
+ tsx: "4.20.6",
239
+ typescript: "catalog:",
240
+ vitest: "^4.0.7"
241
+ },
242
+ engines: {
243
+ node: ">=20.0.0"
244
+ }
245
+ };
246
+ }
247
+ });
248
+
249
+ // src/telemetry/client.ts
250
+ function getTelemetryClient() {
251
+ if (!telemetryInstance) {
252
+ telemetryInstance = new TelemetryClient();
253
+ }
254
+ return telemetryInstance;
255
+ }
256
+ var DEFAULT_ENDPOINT, DEFAULT_TIMEOUT, TelemetryClient, telemetryInstance;
257
+ var init_client = __esm({
258
+ "src/telemetry/client.ts"() {
259
+ "use strict";
260
+ init_esm_shims();
261
+ init_ci_detection();
262
+ init_config();
263
+ DEFAULT_ENDPOINT = "https://wraps.dev/api/telemetry";
264
+ DEFAULT_TIMEOUT = 2e3;
265
+ TelemetryClient = class {
266
+ config;
267
+ endpoint;
268
+ timeout;
269
+ debug;
270
+ enabled;
271
+ eventQueue = [];
272
+ flushTimer;
273
+ constructor(options = {}) {
274
+ this.config = new TelemetryConfigManager();
275
+ this.endpoint = options.endpoint || DEFAULT_ENDPOINT;
276
+ this.timeout = options.timeout || DEFAULT_TIMEOUT;
277
+ this.debug = options.debug || process.env.WRAPS_TELEMETRY_DEBUG === "1";
278
+ this.enabled = this.shouldBeEnabled();
279
+ }
280
+ /**
281
+ * Determine if telemetry should be enabled based on environment and config
282
+ */
283
+ shouldBeEnabled() {
284
+ if (process.env.DO_NOT_TRACK === "1") {
285
+ return false;
286
+ }
287
+ if (process.env.WRAPS_TELEMETRY_DISABLED === "1") {
288
+ return false;
289
+ }
290
+ if (isCI()) {
291
+ return false;
292
+ }
293
+ if (!this.config.isEnabled()) {
294
+ return false;
295
+ }
296
+ return true;
297
+ }
298
+ /**
299
+ * Track an event
300
+ *
301
+ * @param event - Event name in format "category:action" (e.g., "command:init")
302
+ * @param properties - Additional event properties (no PII)
303
+ */
304
+ track(event, properties) {
305
+ const telemetryEvent = {
306
+ event,
307
+ properties: {
308
+ ...properties,
309
+ cli_version: this.getCLIVersion(),
310
+ os: process.platform,
311
+ node_version: process.version,
312
+ ci: isCI()
313
+ },
314
+ anonymousId: this.config.getAnonymousId(),
315
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
316
+ };
317
+ if (this.debug) {
318
+ console.log(
319
+ "[Telemetry Debug] Event:",
320
+ JSON.stringify(telemetryEvent, null, 2)
321
+ );
322
+ return;
323
+ }
324
+ if (!this.enabled) {
325
+ return;
326
+ }
327
+ this.eventQueue.push(telemetryEvent);
328
+ if (this.flushTimer) {
329
+ clearTimeout(this.flushTimer);
330
+ }
331
+ this.flushTimer = setTimeout(() => this.flush(), 100);
332
+ }
333
+ /**
334
+ * Flush queued events to server
335
+ */
336
+ async flush() {
337
+ if (this.eventQueue.length === 0) {
338
+ return;
339
+ }
340
+ const eventsToSend = [...this.eventQueue];
341
+ this.eventQueue = [];
342
+ try {
343
+ const controller = new AbortController();
344
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
345
+ const requestBody = {
346
+ events: eventsToSend,
347
+ batch: true
348
+ };
349
+ await fetch(this.endpoint, {
350
+ method: "POST",
351
+ headers: {
352
+ "Content-Type": "application/json"
353
+ },
354
+ body: JSON.stringify(requestBody),
355
+ signal: controller.signal
356
+ });
357
+ clearTimeout(timeoutId);
358
+ } catch (error) {
359
+ if (this.debug) {
360
+ console.error("[Telemetry Debug] Failed to send events:", error);
361
+ }
362
+ }
363
+ }
364
+ /**
365
+ * Flush and wait for all events to be sent
366
+ * Should be called before CLI exits
367
+ */
368
+ async shutdown() {
369
+ if (this.flushTimer) {
370
+ clearTimeout(this.flushTimer);
371
+ }
372
+ await this.flush();
373
+ }
374
+ /**
375
+ * Enable telemetry
376
+ * Note: Won't override environment variable opt-outs (DO_NOT_TRACK, CI, etc.)
377
+ */
378
+ enable() {
379
+ this.config.setEnabled(true);
380
+ if (process.env.DO_NOT_TRACK === "1" || process.env.DO_NOT_TRACK === "true") {
381
+ this.enabled = false;
382
+ return;
383
+ }
384
+ if (process.env.WRAPS_TELEMETRY_DISABLED === "1") {
385
+ this.enabled = false;
386
+ return;
387
+ }
388
+ if (isCI()) {
389
+ this.enabled = false;
390
+ return;
391
+ }
392
+ this.enabled = true;
393
+ }
394
+ /**
395
+ * Disable telemetry
396
+ */
397
+ disable() {
398
+ this.config.setEnabled(false);
399
+ this.enabled = false;
400
+ this.eventQueue = [];
401
+ }
402
+ /**
403
+ * Check if telemetry is enabled
404
+ */
405
+ isEnabled() {
406
+ return this.enabled;
407
+ }
408
+ /**
409
+ * Get config file path
410
+ */
411
+ getConfigPath() {
412
+ return this.config.getConfigPath();
413
+ }
414
+ /**
415
+ * Show first-run notification
416
+ */
417
+ shouldShowNotification() {
418
+ return this.enabled && !this.config.hasShownNotification();
419
+ }
420
+ /**
421
+ * Mark notification as shown
422
+ */
423
+ markNotificationShown() {
424
+ this.config.markNotificationShown();
425
+ }
426
+ /**
427
+ * Get CLI version from package.json
428
+ */
429
+ getCLIVersion() {
430
+ try {
431
+ const packageJson2 = require_package();
432
+ return packageJson2.version;
433
+ } catch {
434
+ return "unknown";
435
+ }
436
+ }
437
+ };
438
+ telemetryInstance = null;
439
+ }
440
+ });
441
+
442
+ // src/telemetry/events.ts
443
+ function trackCommand(command, metadata) {
444
+ const client = getTelemetryClient();
445
+ const sanitized = metadata ? { ...metadata } : {};
446
+ sanitized.domain = void 0;
447
+ sanitized.accountId = void 0;
448
+ sanitized.email = void 0;
449
+ client.track(`command:${command}`, sanitized);
450
+ }
451
+ function trackError(errorCode, command, metadata) {
452
+ const client = getTelemetryClient();
453
+ client.track("error:occurred", {
454
+ error_code: errorCode,
455
+ command,
456
+ ...metadata
457
+ });
458
+ }
459
+ var init_events = __esm({
460
+ "src/telemetry/events.ts"() {
461
+ "use strict";
462
+ init_esm_shims();
463
+ init_client();
464
+ }
465
+ });
466
+
21
467
  // src/utils/shared/errors.ts
22
468
  import * as clack from "@clack/prompts";
23
469
  import pc from "picocolors";
24
470
  function handleCLIError(error) {
25
471
  console.error("");
26
472
  if (error instanceof WrapsError) {
473
+ trackError(error.code, "unknown");
27
474
  clack.log.error(error.message);
28
475
  if (error.suggestion) {
29
476
  console.log(`
@@ -38,6 +485,7 @@ ${pc.yellow("Suggestion:")}`);
38
485
  }
39
486
  process.exit(1);
40
487
  }
488
+ trackError("UNKNOWN_ERROR", "unknown");
41
489
  clack.log.error("An unexpected error occurred");
42
490
  console.error(error);
43
491
  console.log(`
@@ -51,6 +499,7 @@ var init_errors = __esm({
51
499
  "src/utils/shared/errors.ts"() {
52
500
  "use strict";
53
501
  init_esm_shims();
502
+ init_events();
54
503
  WrapsError = class extends Error {
55
504
  constructor(message, code, suggestion, docsUrl) {
56
505
  super(message);
@@ -2206,9 +2655,9 @@ init_esm_shims();
2206
2655
  import { readFileSync } from "fs";
2207
2656
  import { dirname as dirname2, join as join4 } from "path";
2208
2657
  import { fileURLToPath as fileURLToPath4 } from "url";
2209
- import * as clack13 from "@clack/prompts";
2658
+ import * as clack14 from "@clack/prompts";
2210
2659
  import args from "args";
2211
- import pc14 from "picocolors";
2660
+ import pc15 from "picocolors";
2212
2661
 
2213
2662
  // src/commands/dashboard/update-role.ts
2214
2663
  init_esm_shims();
@@ -2347,7 +2796,9 @@ function createConnectionMetadata(accountId, region, provider, emailConfig, pres
2347
2796
  function applyConfigUpdates(existingConfig, updates) {
2348
2797
  const result = { ...existingConfig };
2349
2798
  for (const [key, value] of Object.entries(updates)) {
2350
- if (value === void 0) continue;
2799
+ if (value === void 0) {
2800
+ continue;
2801
+ }
2351
2802
  if (key === "tracking" && typeof value === "object") {
2352
2803
  const trackingUpdate = value;
2353
2804
  result.tracking = {
@@ -2820,6 +3271,7 @@ ${pc3.bold("Updated Permissions:")}`);
2820
3271
  console.log(
2821
3272
  ` ${pc3.green("\u2713")} SES metrics and identity verification (always enabled)`
2822
3273
  );
3274
+ console.log(` ${pc3.green("\u2713")} SES template management (always enabled)`);
2823
3275
  if (sendingEnabled) {
2824
3276
  console.log(` ${pc3.green("\u2713")} Email sending via SES`);
2825
3277
  }
@@ -2853,6 +3305,18 @@ function buildConsolePolicyDocument(emailConfig) {
2853
3305
  ],
2854
3306
  Resource: "*"
2855
3307
  });
3308
+ statements.push({
3309
+ Effect: "Allow",
3310
+ Action: [
3311
+ "ses:GetTemplate",
3312
+ "ses:ListTemplates",
3313
+ "ses:CreateTemplate",
3314
+ "ses:UpdateTemplate",
3315
+ "ses:DeleteTemplate",
3316
+ "ses:TestRenderTemplate"
3317
+ ],
3318
+ Resource: "*"
3319
+ });
2856
3320
  const sendingEnabled = !emailConfig || emailConfig.sendingEnabled !== false;
2857
3321
  if (sendingEnabled) {
2858
3322
  statements.push({
@@ -3924,7 +4388,14 @@ ${pc4.bold("Current Configuration:")}
3924
4388
  } else {
3925
4389
  console.log(` Preset: ${pc4.cyan("custom")}`);
3926
4390
  }
3927
- const config2 = metadata.services.email.config;
4391
+ const config2 = metadata.services.email?.config;
4392
+ if (!config2) {
4393
+ clack3.log.error("No email configuration found in metadata");
4394
+ clack3.log.info(
4395
+ `Use ${pc4.cyan("wraps email init")} to create new infrastructure.`
4396
+ );
4397
+ process.exit(1);
4398
+ }
3928
4399
  if (config2.domain) {
3929
4400
  console.log(` Sending Domain: ${pc4.cyan(config2.domain)}`);
3930
4401
  }
@@ -5213,13 +5684,13 @@ async function restore(options) {
5213
5684
  ${pc9.bold("The following Wraps resources will be removed:")}
5214
5685
  `
5215
5686
  );
5216
- if (metadata.services.email.config.tracking?.enabled) {
5687
+ if (metadata.services.email?.config.tracking?.enabled) {
5217
5688
  console.log(` ${pc9.cyan("\u2713")} Configuration Set (wraps-email-tracking)`);
5218
5689
  }
5219
- if (metadata.services.email.config.eventTracking?.dynamoDBHistory) {
5690
+ if (metadata.services.email?.config.eventTracking?.dynamoDBHistory) {
5220
5691
  console.log(` ${pc9.cyan("\u2713")} DynamoDB Table (wraps-email-history)`);
5221
5692
  }
5222
- if (metadata.services.email.config.eventTracking?.enabled) {
5693
+ if (metadata.services.email?.config.eventTracking?.enabled) {
5223
5694
  console.log(` ${pc9.cyan("\u2713")} EventBridge Rules`);
5224
5695
  console.log(` ${pc9.cyan("\u2713")} SQS Queues`);
5225
5696
  console.log(` ${pc9.cyan("\u2713")} Lambda Functions`);
@@ -5331,7 +5802,14 @@ ${pc10.bold("Current Configuration:")}
5331
5802
  } else {
5332
5803
  console.log(` Preset: ${pc10.cyan("custom")}`);
5333
5804
  }
5334
- const config2 = metadata.services.email.config;
5805
+ const config2 = metadata.services.email?.config;
5806
+ if (!config2) {
5807
+ clack9.log.error("No email configuration found in metadata");
5808
+ clack9.log.info(
5809
+ `Use ${pc10.cyan("wraps email init")} to create new infrastructure.`
5810
+ );
5811
+ process.exit(1);
5812
+ }
5335
5813
  if (config2.domain) {
5336
5814
  console.log(` Sending Domain: ${pc10.cyan(config2.domain)}`);
5337
5815
  }
@@ -6989,7 +7467,7 @@ function createSettingsRouter(config2) {
6989
7467
  });
6990
7468
  }
6991
7469
  const configSetName = "wraps-email-tracking";
6992
- const domain = metadata.services.email.config.domain;
7470
+ const domain = metadata.services.email?.config.domain;
6993
7471
  const settings = await fetchEmailSettings(
6994
7472
  config2.roleArn,
6995
7473
  config2.region,
@@ -7548,6 +8026,67 @@ Run ${pc13.cyan("wraps email init")} to deploy infrastructure.
7548
8026
  });
7549
8027
  }
7550
8028
 
8029
+ // src/commands/telemetry.ts
8030
+ init_esm_shims();
8031
+ init_client();
8032
+ import * as clack13 from "@clack/prompts";
8033
+ import pc14 from "picocolors";
8034
+ async function telemetryEnable() {
8035
+ const client = getTelemetryClient();
8036
+ client.enable();
8037
+ clack13.log.success(pc14.green("Telemetry enabled"));
8038
+ console.log(` Config: ${pc14.dim(client.getConfigPath())}`);
8039
+ console.log(`
8040
+ ${pc14.dim("Thank you for helping improve Wraps!")}
8041
+ `);
8042
+ }
8043
+ async function telemetryDisable() {
8044
+ const client = getTelemetryClient();
8045
+ client.disable();
8046
+ clack13.log.success(pc14.green("Telemetry disabled"));
8047
+ console.log(` Config: ${pc14.dim(client.getConfigPath())}`);
8048
+ console.log(
8049
+ `
8050
+ ${pc14.dim("You can re-enable with:")} wraps telemetry enable
8051
+ `
8052
+ );
8053
+ }
8054
+ async function telemetryStatus() {
8055
+ const client = getTelemetryClient();
8056
+ clack13.intro(pc14.bold("Telemetry Status"));
8057
+ const status2 = client.isEnabled() ? pc14.green("Enabled") : pc14.red("Disabled");
8058
+ console.log();
8059
+ console.log(` ${pc14.bold("Status:")} ${status2}`);
8060
+ console.log(` ${pc14.bold("Config file:")} ${pc14.dim(client.getConfigPath())}`);
8061
+ if (client.isEnabled()) {
8062
+ console.log();
8063
+ console.log(pc14.bold(" How to opt-out:"));
8064
+ console.log(` ${pc14.cyan("wraps telemetry disable")}`);
8065
+ console.log(
8066
+ ` ${pc14.dim("Or set:")} ${pc14.cyan("WRAPS_TELEMETRY_DISABLED=1")}`
8067
+ );
8068
+ console.log(` ${pc14.dim("Or set:")} ${pc14.cyan("DO_NOT_TRACK=1")}`);
8069
+ } else {
8070
+ console.log();
8071
+ console.log(pc14.bold(" How to opt-in:"));
8072
+ console.log(` ${pc14.cyan("wraps telemetry enable")}`);
8073
+ }
8074
+ console.log();
8075
+ console.log(pc14.bold(" Debug mode:"));
8076
+ console.log(
8077
+ ` ${pc14.dim("See what would be sent:")} ${pc14.cyan("WRAPS_TELEMETRY_DEBUG=1 wraps <command>")}`
8078
+ );
8079
+ console.log();
8080
+ console.log(
8081
+ ` ${pc14.dim("Learn more:")} ${pc14.cyan("https://wraps.dev/docs/telemetry")}`
8082
+ );
8083
+ console.log();
8084
+ }
8085
+
8086
+ // src/cli.ts
8087
+ init_client();
8088
+ init_events();
8089
+
7551
8090
  // src/utils/shared/completion.ts
7552
8091
  init_esm_shims();
7553
8092
  function setupTabCompletion() {
@@ -7586,56 +8125,57 @@ function showVersion() {
7586
8125
  process.exit(0);
7587
8126
  }
7588
8127
  function showHelp() {
7589
- clack13.intro(pc14.bold(`WRAPS CLI v${VERSION}`));
8128
+ clack14.intro(pc15.bold(`WRAPS CLI v${VERSION}`));
7590
8129
  console.log("Deploy AWS infrastructure to your account\n");
7591
8130
  console.log("Usage: wraps [service] <command> [options]\n");
7592
8131
  console.log("Services:");
7593
- console.log(` ${pc14.cyan("email")} Email infrastructure (AWS SES)`);
8132
+ console.log(` ${pc15.cyan("email")} Email infrastructure (AWS SES)`);
7594
8133
  console.log(
7595
- ` ${pc14.cyan("sms")} SMS infrastructure (AWS End User Messaging) ${pc14.dim("[coming soon]")}
8134
+ ` ${pc15.cyan("sms")} SMS infrastructure (AWS End User Messaging) ${pc15.dim("[coming soon]")}
7596
8135
  `
7597
8136
  );
7598
8137
  console.log("Email Commands:");
7599
8138
  console.log(
7600
- ` ${pc14.cyan("email init")} Deploy new email infrastructure`
8139
+ ` ${pc15.cyan("email init")} Deploy new email infrastructure`
7601
8140
  );
7602
8141
  console.log(
7603
- ` ${pc14.cyan("email connect")} Connect to existing AWS SES`
8142
+ ` ${pc15.cyan("email connect")} Connect to existing AWS SES`
7604
8143
  );
7605
- console.log(` ${pc14.cyan("email domains verify")} Verify domain DNS records`);
7606
- console.log(` ${pc14.cyan("email config")} Update infrastructure`);
7607
- console.log(` ${pc14.cyan("email upgrade")} Add features`);
8144
+ console.log(` ${pc15.cyan("email domains verify")} Verify domain DNS records`);
8145
+ console.log(` ${pc15.cyan("email config")} Update infrastructure`);
8146
+ console.log(` ${pc15.cyan("email upgrade")} Add features`);
7608
8147
  console.log(
7609
- ` ${pc14.cyan("email restore")} Restore original configuration
8148
+ ` ${pc15.cyan("email restore")} Restore original configuration
7610
8149
  `
7611
8150
  );
7612
8151
  console.log("Console & Dashboard:");
7613
- console.log(` ${pc14.cyan("console")} Start local web console`);
8152
+ console.log(` ${pc15.cyan("console")} Start local web console`);
7614
8153
  console.log(
7615
- ` ${pc14.cyan("dashboard update-role")} Update hosted dashboard IAM permissions
8154
+ ` ${pc15.cyan("dashboard update-role")} Update hosted dashboard IAM permissions
7616
8155
  `
7617
8156
  );
7618
8157
  console.log("Global Commands:");
7619
- console.log(` ${pc14.cyan("status")} Show all infrastructure status`);
7620
- console.log(` ${pc14.cyan("destroy")} Remove deployed infrastructure`);
8158
+ console.log(` ${pc15.cyan("status")} Show all infrastructure status`);
8159
+ console.log(` ${pc15.cyan("destroy")} Remove deployed infrastructure`);
8160
+ console.log(` ${pc15.cyan("completion")} Generate shell completion script`);
7621
8161
  console.log(
7622
- ` ${pc14.cyan("completion")} Generate shell completion script
8162
+ ` ${pc15.cyan("telemetry")} Manage anonymous telemetry settings
7623
8163
  `
7624
8164
  );
7625
8165
  console.log("Options:");
7626
8166
  console.log(
7627
- ` ${pc14.dim("-p, --provider")} Hosting provider (vercel, aws, railway, other)`
8167
+ ` ${pc15.dim("-p, --provider")} Hosting provider (vercel, aws, railway, other)`
7628
8168
  );
7629
- console.log(` ${pc14.dim("-r, --region")} AWS region`);
7630
- console.log(` ${pc14.dim("-d, --domain")} Domain name`);
7631
- console.log(` ${pc14.dim("--account")} AWS account ID or alias`);
7632
- console.log(` ${pc14.dim("--preset")} Configuration preset`);
7633
- console.log(` ${pc14.dim("-y, --yes")} Skip confirmation prompts`);
7634
- console.log(` ${pc14.dim("-f, --force")} Force destructive operations`);
7635
- console.log(` ${pc14.dim("-v, --version")} Show version number
8169
+ console.log(` ${pc15.dim("-r, --region")} AWS region`);
8170
+ console.log(` ${pc15.dim("-d, --domain")} Domain name`);
8171
+ console.log(` ${pc15.dim("--account")} AWS account ID or alias`);
8172
+ console.log(` ${pc15.dim("--preset")} Configuration preset`);
8173
+ console.log(` ${pc15.dim("-y, --yes")} Skip confirmation prompts`);
8174
+ console.log(` ${pc15.dim("-f, --force")} Force destructive operations`);
8175
+ console.log(` ${pc15.dim("-v, --version")} Show version number
7636
8176
  `);
7637
8177
  console.log(
7638
- `Run ${pc14.cyan("wraps <service> <command> --help")} for more information.
8178
+ `Run ${pc15.cyan("wraps <service> <command> --help")} for more information.
7639
8179
  `
7640
8180
  );
7641
8181
  process.exit(0);
@@ -7697,9 +8237,9 @@ var flags = args.parse(process.argv);
7697
8237
  var [primaryCommand, subCommand] = args.sub;
7698
8238
  if (!primaryCommand) {
7699
8239
  async function selectService() {
7700
- clack13.intro(pc14.bold(`WRAPS CLI v${VERSION}`));
8240
+ clack14.intro(pc15.bold(`WRAPS CLI v${VERSION}`));
7701
8241
  console.log("Welcome! Let's get started deploying your infrastructure.\n");
7702
- const service = await clack13.select({
8242
+ const service = await clack14.select({
7703
8243
  message: "Which service would you like to set up?",
7704
8244
  options: [
7705
8245
  {
@@ -7714,20 +8254,20 @@ if (!primaryCommand) {
7714
8254
  }
7715
8255
  ]
7716
8256
  });
7717
- if (clack13.isCancel(service)) {
7718
- clack13.cancel("Operation cancelled.");
8257
+ if (clack14.isCancel(service)) {
8258
+ clack14.cancel("Operation cancelled.");
7719
8259
  process.exit(0);
7720
8260
  }
7721
8261
  if (service === "sms") {
7722
- clack13.log.warn("SMS infrastructure is coming soon!");
8262
+ clack14.log.warn("SMS infrastructure is coming soon!");
7723
8263
  console.log(
7724
8264
  `
7725
- Check back soon or follow our progress at ${pc14.cyan("https://github.com/wraps-team/wraps")}
8265
+ Check back soon or follow our progress at ${pc15.cyan("https://github.com/wraps-team/wraps")}
7726
8266
  `
7727
8267
  );
7728
8268
  process.exit(0);
7729
8269
  }
7730
- const action = await clack13.select({
8270
+ const action = await clack14.select({
7731
8271
  message: "What would you like to do?",
7732
8272
  options: [
7733
8273
  {
@@ -7742,8 +8282,8 @@ Check back soon or follow our progress at ${pc14.cyan("https://github.com/wraps-
7742
8282
  }
7743
8283
  ]
7744
8284
  });
7745
- if (clack13.isCancel(action)) {
7746
- clack13.cancel("Operation cancelled.");
8285
+ if (clack14.isCancel(action)) {
8286
+ clack14.cancel("Operation cancelled.");
7747
8287
  process.exit(0);
7748
8288
  }
7749
8289
  if (action === "init") {
@@ -7766,6 +8306,27 @@ Check back soon or follow our progress at ${pc14.cyan("https://github.com/wraps-
7766
8306
  process.exit(0);
7767
8307
  }
7768
8308
  async function run() {
8309
+ const startTime = Date.now();
8310
+ const telemetry = getTelemetryClient();
8311
+ if (telemetry.shouldShowNotification()) {
8312
+ console.log();
8313
+ clack14.log.info(pc15.bold("Anonymous Telemetry"));
8314
+ console.log(
8315
+ ` Wraps collects ${pc15.cyan("anonymous usage data")} to improve the CLI.`
8316
+ );
8317
+ console.log(
8318
+ ` We ${pc15.bold("never")} collect: domains, AWS credentials, email content, or PII.`
8319
+ );
8320
+ console.log(
8321
+ ` We ${pc15.bold("only")} collect: command names, success/failure, CLI version, OS.`
8322
+ );
8323
+ console.log();
8324
+ console.log(` Opt-out anytime: ${pc15.cyan("wraps telemetry disable")}`);
8325
+ console.log(` Or set: ${pc15.cyan("WRAPS_TELEMETRY_DISABLED=1")}`);
8326
+ console.log(` Learn more: ${pc15.cyan("https://wraps.dev/docs/telemetry")}`);
8327
+ console.log();
8328
+ telemetry.markNotificationShown();
8329
+ }
7769
8330
  try {
7770
8331
  if (primaryCommand === "email" && subCommand) {
7771
8332
  switch (subCommand) {
@@ -7808,10 +8369,10 @@ async function run() {
7808
8369
  switch (domainsSubCommand) {
7809
8370
  case "add": {
7810
8371
  if (!flags.domain) {
7811
- clack13.log.error("--domain flag is required");
8372
+ clack14.log.error("--domain flag is required");
7812
8373
  console.log(
7813
8374
  `
7814
- Usage: ${pc14.cyan("wraps email domains add --domain yourapp.com")}
8375
+ Usage: ${pc15.cyan("wraps email domains add --domain yourapp.com")}
7815
8376
  `
7816
8377
  );
7817
8378
  process.exit(1);
@@ -7824,10 +8385,10 @@ Usage: ${pc14.cyan("wraps email domains add --domain yourapp.com")}
7824
8385
  break;
7825
8386
  case "verify": {
7826
8387
  if (!flags.domain) {
7827
- clack13.log.error("--domain flag is required");
8388
+ clack14.log.error("--domain flag is required");
7828
8389
  console.log(
7829
8390
  `
7830
- Usage: ${pc14.cyan("wraps email domains verify --domain yourapp.com")}
8391
+ Usage: ${pc15.cyan("wraps email domains verify --domain yourapp.com")}
7831
8392
  `
7832
8393
  );
7833
8394
  process.exit(1);
@@ -7837,10 +8398,10 @@ Usage: ${pc14.cyan("wraps email domains verify --domain yourapp.com")}
7837
8398
  }
7838
8399
  case "get-dkim": {
7839
8400
  if (!flags.domain) {
7840
- clack13.log.error("--domain flag is required");
8401
+ clack14.log.error("--domain flag is required");
7841
8402
  console.log(
7842
8403
  `
7843
- Usage: ${pc14.cyan("wraps email domains get-dkim --domain yourapp.com")}
8404
+ Usage: ${pc15.cyan("wraps email domains get-dkim --domain yourapp.com")}
7844
8405
  `
7845
8406
  );
7846
8407
  process.exit(1);
@@ -7850,10 +8411,10 @@ Usage: ${pc14.cyan("wraps email domains get-dkim --domain yourapp.com")}
7850
8411
  }
7851
8412
  case "remove": {
7852
8413
  if (!flags.domain) {
7853
- clack13.log.error("--domain flag is required");
8414
+ clack14.log.error("--domain flag is required");
7854
8415
  console.log(
7855
8416
  `
7856
- Usage: ${pc14.cyan("wraps email domains remove --domain yourapp.com --force")}
8417
+ Usage: ${pc15.cyan("wraps email domains remove --domain yourapp.com --force")}
7857
8418
  `
7858
8419
  );
7859
8420
  process.exit(1);
@@ -7865,12 +8426,12 @@ Usage: ${pc14.cyan("wraps email domains remove --domain yourapp.com --force")}
7865
8426
  break;
7866
8427
  }
7867
8428
  default:
7868
- clack13.log.error(
8429
+ clack14.log.error(
7869
8430
  `Unknown domains command: ${domainsSubCommand || "(none)"}`
7870
8431
  );
7871
8432
  console.log(
7872
8433
  `
7873
- Available commands: ${pc14.cyan("add")}, ${pc14.cyan("list")}, ${pc14.cyan("verify")}, ${pc14.cyan("get-dkim")}, ${pc14.cyan("remove")}
8434
+ Available commands: ${pc15.cyan("add")}, ${pc15.cyan("list")}, ${pc15.cyan("verify")}, ${pc15.cyan("get-dkim")}, ${pc15.cyan("remove")}
7874
8435
  `
7875
8436
  );
7876
8437
  process.exit(1);
@@ -7878,10 +8439,10 @@ Available commands: ${pc14.cyan("add")}, ${pc14.cyan("list")}, ${pc14.cyan("veri
7878
8439
  break;
7879
8440
  }
7880
8441
  default:
7881
- clack13.log.error(`Unknown email command: ${subCommand}`);
8442
+ clack14.log.error(`Unknown email command: ${subCommand}`);
7882
8443
  console.log(
7883
8444
  `
7884
- Run ${pc14.cyan("wraps --help")} for available commands.
8445
+ Run ${pc15.cyan("wraps --help")} for available commands.
7885
8446
  `
7886
8447
  );
7887
8448
  process.exit(1);
@@ -7897,21 +8458,21 @@ Run ${pc14.cyan("wraps --help")} for available commands.
7897
8458
  });
7898
8459
  break;
7899
8460
  default:
7900
- clack13.log.error(`Unknown dashboard command: ${subCommand}`);
8461
+ clack14.log.error(`Unknown dashboard command: ${subCommand}`);
7901
8462
  console.log(`
7902
- Available commands: ${pc14.cyan("update-role")}
8463
+ Available commands: ${pc15.cyan("update-role")}
7903
8464
  `);
7904
- console.log(`Run ${pc14.cyan("wraps --help")} for more information.
8465
+ console.log(`Run ${pc15.cyan("wraps --help")} for more information.
7905
8466
  `);
7906
8467
  process.exit(1);
7907
8468
  }
7908
8469
  return;
7909
8470
  }
7910
8471
  if (primaryCommand === "sms" && subCommand) {
7911
- clack13.log.warn("SMS infrastructure is coming soon!");
8472
+ clack14.log.warn("SMS infrastructure is coming soon!");
7912
8473
  console.log(
7913
8474
  `
7914
- Check back soon or follow our progress at ${pc14.cyan("https://github.com/wraps-team/wraps")}
8475
+ Check back soon or follow our progress at ${pc15.cyan("https://github.com/wraps-team/wraps")}
7915
8476
  `
7916
8477
  );
7917
8478
  process.exit(0);
@@ -7931,8 +8492,8 @@ Check back soon or follow our progress at ${pc14.cyan("https://github.com/wraps-
7931
8492
  break;
7932
8493
  case "dashboard":
7933
8494
  if (!subCommand) {
7934
- clack13.log.warn(
7935
- `'wraps dashboard' is deprecated. Use ${pc14.cyan("wraps console")} instead.`
8495
+ clack14.log.warn(
8496
+ `'wraps dashboard' is deprecated. Use ${pc15.cyan("wraps console")} instead.`
7936
8497
  );
7937
8498
  await dashboard({
7938
8499
  port: flags.port,
@@ -7948,6 +8509,29 @@ Check back soon or follow our progress at ${pc14.cyan("https://github.com/wraps-
7948
8509
  case "completion":
7949
8510
  printCompletionScript();
7950
8511
  break;
8512
+ case "telemetry": {
8513
+ switch (subCommand) {
8514
+ case "enable":
8515
+ await telemetryEnable();
8516
+ break;
8517
+ case "disable":
8518
+ await telemetryDisable();
8519
+ break;
8520
+ case "status":
8521
+ case void 0:
8522
+ await telemetryStatus();
8523
+ break;
8524
+ default:
8525
+ clack14.log.error(`Unknown telemetry command: ${subCommand}`);
8526
+ console.log(
8527
+ `
8528
+ Available commands: ${pc15.cyan("enable")}, ${pc15.cyan("disable")}, ${pc15.cyan("status")}
8529
+ `
8530
+ );
8531
+ process.exit(1);
8532
+ }
8533
+ break;
8534
+ }
7951
8535
  // Show help for service without subcommand
7952
8536
  case "email":
7953
8537
  case "sms":
@@ -7959,16 +8543,30 @@ Please specify a command for ${primaryCommand} service.
7959
8543
  showHelp();
7960
8544
  break;
7961
8545
  default:
7962
- clack13.log.error(`Unknown command: ${primaryCommand}`);
8546
+ clack14.log.error(`Unknown command: ${primaryCommand}`);
7963
8547
  console.log(
7964
8548
  `
7965
- Run ${pc14.cyan("wraps --help")} for available commands.
8549
+ Run ${pc15.cyan("wraps --help")} for available commands.
7966
8550
  `
7967
8551
  );
7968
8552
  process.exit(1);
7969
8553
  }
8554
+ const duration = Date.now() - startTime;
8555
+ const commandName = subCommand ? `${primaryCommand}:${subCommand}` : primaryCommand;
8556
+ trackCommand(commandName, {
8557
+ success: true,
8558
+ duration_ms: duration
8559
+ });
7970
8560
  } catch (error) {
8561
+ const duration = Date.now() - startTime;
8562
+ const commandName = subCommand ? `${primaryCommand}:${subCommand}` : primaryCommand;
8563
+ trackCommand(commandName, {
8564
+ success: false,
8565
+ duration_ms: duration
8566
+ });
7971
8567
  handleCLIError(error);
8568
+ } finally {
8569
+ await telemetry.shutdown();
7972
8570
  }
7973
8571
  }
7974
8572
  run();