userdispatch 0.1.0 → 0.2.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.
Files changed (2) hide show
  1. package/dist/index.js +234 -45
  2. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -1,4 +1,102 @@
1
1
  #!/usr/bin/env node
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __commonJS = (cb, mod) => function __require() {
9
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+
28
+ // node_modules/picocolors/picocolors.js
29
+ var require_picocolors = __commonJS({
30
+ "node_modules/picocolors/picocolors.js"(exports, module) {
31
+ "use strict";
32
+ var p = process || {};
33
+ var argv = p.argv || [];
34
+ var env = p.env || {};
35
+ var isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
36
+ var formatter = (open, close, replace = open) => (input) => {
37
+ let string = "" + input, index = string.indexOf(close, open.length);
38
+ return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
39
+ };
40
+ var replaceClose = (string, close, replace, index) => {
41
+ let result = "", cursor = 0;
42
+ do {
43
+ result += string.substring(cursor, index) + replace;
44
+ cursor = index + close.length;
45
+ index = string.indexOf(close, cursor);
46
+ } while (~index);
47
+ return result + string.substring(cursor);
48
+ };
49
+ var createColors = (enabled = isColorSupported) => {
50
+ let f = enabled ? formatter : () => String;
51
+ return {
52
+ isColorSupported: enabled,
53
+ reset: f("\x1B[0m", "\x1B[0m"),
54
+ bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
55
+ dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
56
+ italic: f("\x1B[3m", "\x1B[23m"),
57
+ underline: f("\x1B[4m", "\x1B[24m"),
58
+ inverse: f("\x1B[7m", "\x1B[27m"),
59
+ hidden: f("\x1B[8m", "\x1B[28m"),
60
+ strikethrough: f("\x1B[9m", "\x1B[29m"),
61
+ black: f("\x1B[30m", "\x1B[39m"),
62
+ red: f("\x1B[31m", "\x1B[39m"),
63
+ green: f("\x1B[32m", "\x1B[39m"),
64
+ yellow: f("\x1B[33m", "\x1B[39m"),
65
+ blue: f("\x1B[34m", "\x1B[39m"),
66
+ magenta: f("\x1B[35m", "\x1B[39m"),
67
+ cyan: f("\x1B[36m", "\x1B[39m"),
68
+ white: f("\x1B[37m", "\x1B[39m"),
69
+ gray: f("\x1B[90m", "\x1B[39m"),
70
+ bgBlack: f("\x1B[40m", "\x1B[49m"),
71
+ bgRed: f("\x1B[41m", "\x1B[49m"),
72
+ bgGreen: f("\x1B[42m", "\x1B[49m"),
73
+ bgYellow: f("\x1B[43m", "\x1B[49m"),
74
+ bgBlue: f("\x1B[44m", "\x1B[49m"),
75
+ bgMagenta: f("\x1B[45m", "\x1B[49m"),
76
+ bgCyan: f("\x1B[46m", "\x1B[49m"),
77
+ bgWhite: f("\x1B[47m", "\x1B[49m"),
78
+ blackBright: f("\x1B[90m", "\x1B[39m"),
79
+ redBright: f("\x1B[91m", "\x1B[39m"),
80
+ greenBright: f("\x1B[92m", "\x1B[39m"),
81
+ yellowBright: f("\x1B[93m", "\x1B[39m"),
82
+ blueBright: f("\x1B[94m", "\x1B[39m"),
83
+ magentaBright: f("\x1B[95m", "\x1B[39m"),
84
+ cyanBright: f("\x1B[96m", "\x1B[39m"),
85
+ whiteBright: f("\x1B[97m", "\x1B[39m"),
86
+ bgBlackBright: f("\x1B[100m", "\x1B[49m"),
87
+ bgRedBright: f("\x1B[101m", "\x1B[49m"),
88
+ bgGreenBright: f("\x1B[102m", "\x1B[49m"),
89
+ bgYellowBright: f("\x1B[103m", "\x1B[49m"),
90
+ bgBlueBright: f("\x1B[104m", "\x1B[49m"),
91
+ bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
92
+ bgCyanBright: f("\x1B[106m", "\x1B[49m"),
93
+ bgWhiteBright: f("\x1B[107m", "\x1B[49m")
94
+ };
95
+ };
96
+ module.exports = createColors();
97
+ module.exports.createColors = createColors;
98
+ }
99
+ });
2
100
 
3
101
  // src/index.ts
4
102
  import { intro as intro2, outro as outro2 } from "@clack/prompts";
@@ -7,6 +105,7 @@ import { intro as intro2, outro as outro2 } from "@clack/prompts";
7
105
  import { intro, log as log7 } from "@clack/prompts";
8
106
 
9
107
  // src/steps/auth.ts
108
+ var import_picocolors = __toESM(require_picocolors(), 1);
10
109
  import { text, spinner, log, isCancel } from "@clack/prompts";
11
110
  import { exec } from "child_process";
12
111
  import { platform } from "os";
@@ -92,7 +191,8 @@ async function authStep(flags2) {
92
191
  let token = flags2.token;
93
192
  if (!token) {
94
193
  const authUrl = `${PROD_URL}/cli/auth`;
95
- log.step("Step 1 of 6 \u2014 Authenticate");
194
+ log.step(import_picocolors.default.bold("Step 1 of 6 \u2014 Authenticate"));
195
+ log.info("Sign in to link this project to your UserDispatch account. We'll open your browser and generate a token you can paste back here.\n");
96
196
  log.info("Opening your browser to sign in with Google.");
97
197
  log.info("After signing in, copy the token shown on screen and paste it here.");
98
198
  openBrowser(authUrl);
@@ -133,6 +233,7 @@ async function authStep(flags2) {
133
233
  }
134
234
 
135
235
  // src/steps/setup.ts
236
+ var import_picocolors2 = __toESM(require_picocolors(), 1);
136
237
  import { text as text2, spinner as spinner2, log as log2, isCancel as isCancel2 } from "@clack/prompts";
137
238
  import { basename } from "path";
138
239
  function slugify(str) {
@@ -144,18 +245,19 @@ async function promptForValue(message, defaultValue, placeholder) {
144
245
  defaultValue,
145
246
  placeholder: placeholder || defaultValue,
146
247
  validate(value) {
147
- if (!value.trim()) return "This field is required.";
248
+ if (!value.trim() && !defaultValue) return "This field is required.";
148
249
  }
149
250
  });
150
251
  if (isCancel2(result)) {
151
252
  log2.warn("Setup cancelled.");
152
253
  process.exit(0);
153
254
  }
154
- return result.trim();
255
+ return result.trim() || defaultValue;
155
256
  }
156
257
  async function setupStep(auth, flags2 = {}) {
157
258
  const s = spinner2();
158
- log2.step("Step 2 of 6 \u2014 Set up your organization and app");
259
+ log2.step(import_picocolors2.default.bold("Step 2 of 6 \u2014 Set up your organization and app"));
260
+ log2.info("Create the organization and app that will receive feedback. You can add more apps later from the dashboard.\n");
159
261
  let orgName;
160
262
  let orgSlug;
161
263
  if (!auth.org) {
@@ -163,7 +265,9 @@ async function setupStep(auth, flags2 = {}) {
163
265
  orgName = flags2.org;
164
266
  orgSlug = slugify(flags2.org);
165
267
  } else {
268
+ log2.info("This is your team or company name \u2014 shown in the dashboard header.");
166
269
  orgName = await promptForValue("Organization name:", "My Organization");
270
+ log2.info("URL-friendly identifier \u2014 your dashboard will be at userdispatch.com/org/<slug>.");
167
271
  orgSlug = await promptForValue("Organization slug:", slugify(orgName));
168
272
  }
169
273
  } else {
@@ -176,7 +280,9 @@ async function setupStep(auth, flags2 = {}) {
176
280
  appName = flags2.app;
177
281
  appSlug = slugify(flags2.app);
178
282
  } else {
283
+ log2.info("The project you're collecting feedback for \u2014 shown in the widget and dashboard.");
179
284
  appName = await promptForValue("App name:", defaultAppName);
285
+ log2.info("URL-friendly identifier \u2014 feedback form will be at userdispatch.com/f/<org>/<app-slug>.");
180
286
  appSlug = await promptForValue("App slug:", slugify(appName));
181
287
  }
182
288
  s.start("Creating your setup...");
@@ -211,6 +317,7 @@ async function setupStep(auth, flags2 = {}) {
211
317
  }
212
318
 
213
319
  // src/steps/widget.ts
320
+ var import_picocolors3 = __toESM(require_picocolors(), 1);
214
321
  import { spinner as spinner3, log as log3, confirm, isCancel as isCancel3 } from "@clack/prompts";
215
322
  import { execSync } from "child_process";
216
323
  import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
@@ -384,7 +491,8 @@ async function widgetStep(apiKey, flags2 = {}) {
384
491
  const s = spinner3();
385
492
  const pm = detectPackageManager(cwd);
386
493
  const detection = detectFramework(cwd);
387
- log3.step("Step 3 of 6 \u2014 Install widget");
494
+ log3.step(import_picocolors3.default.bold("Step 3 of 6 \u2014 Install widget"));
495
+ log3.info("Add the feedback widget to your app. Users will see a floating button to submit bugs, feedback, and questions.\n");
388
496
  log3.info(`Detected framework: ${detection.label}`);
389
497
  let installed = false;
390
498
  if (!isAlreadyInstalled(cwd)) {
@@ -449,6 +557,7 @@ async function widgetStep(apiKey, flags2 = {}) {
449
557
  }
450
558
 
451
559
  // src/steps/mcp.ts
560
+ var import_picocolors4 = __toESM(require_picocolors(), 1);
452
561
  import { multiselect, spinner as spinner4, log as log4, isCancel as isCancel4 } from "@clack/prompts";
453
562
  import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync } from "fs";
454
563
  import { dirname, relative } from "path";
@@ -496,7 +605,8 @@ async function mcpStep(token, flags2 = {}) {
496
605
  const cwd = process.cwd();
497
606
  const agents = detectAgents(cwd);
498
607
  const detected = agents.filter((a) => a.detected);
499
- log4.step("Step 4 of 6 \u2014 Configure MCP for your coding agent");
608
+ log4.step(import_picocolors4.default.bold("Step 4 of 6 \u2014 Configure MCP for your coding agent"));
609
+ log4.info("Connect UserDispatch to your AI coding agent so it can triage bugs, reply to users, and generate digests.\n");
500
610
  if (detected.length === 0) {
501
611
  log4.info("No coding agents detected. You can configure MCP manually later.");
502
612
  return { configured: [], skipped: true };
@@ -541,10 +651,12 @@ async function mcpStep(token, flags2 = {}) {
541
651
  }
542
652
 
543
653
  // src/steps/verify.ts
654
+ var import_picocolors5 = __toESM(require_picocolors(), 1);
544
655
  import { spinner as spinner5, log as log5 } from "@clack/prompts";
545
656
  async function verifyStep(apiKey) {
546
657
  const s = spinner5();
547
- log5.step("Step 5 of 6 \u2014 Verify connection");
658
+ log5.step(import_picocolors5.default.bold("Step 5 of 6 \u2014 Verify connection"));
659
+ log5.info("Send a test submission to confirm everything is wired up correctly.\n");
548
660
  s.start("Sending a test submission to your dashboard...");
549
661
  try {
550
662
  await apiSubmission(
@@ -571,11 +683,12 @@ async function verifyStep(apiKey) {
571
683
  }
572
684
 
573
685
  // src/steps/summary.ts
686
+ var import_picocolors6 = __toESM(require_picocolors(), 1);
574
687
  import { note, outro, log as log6 } from "@clack/prompts";
575
688
  function summaryStep(result) {
576
689
  const { setup, widget, mcp, verified, modifiedFiles } = result;
577
- log6.step("Step 6 of 6 \u2014 Summary");
578
- const maskedKey = setup.app.apiKey.slice(0, 7) + "..." + setup.app.apiKey.slice(-4);
690
+ log6.step(import_picocolors6.default.bold("Step 6 of 6 \u2014 Summary"));
691
+ const maskedKey = setup.app.apiKey ? setup.app.apiKey.slice(0, 7) + "..." + setup.app.apiKey.slice(-4) : "N/A";
579
692
  const lines = [];
580
693
  lines.push(`Organization: ${setup.org.name} (${setup.org.slug})`);
581
694
  lines.push(`App: ${setup.app.name} (${setup.app.slug})`);
@@ -615,49 +728,125 @@ function summaryStep(result) {
615
728
  outro("You're all set! Happy building.");
616
729
  }
617
730
 
731
+ // src/lib/sentry.ts
732
+ import * as Sentry from "@sentry/node";
733
+ var SENTRY_DSN = "https://9611a3d31c8f0b5d8049c23924d0d4ad@o4510882392899584.ingest.us.sentry.io/4510965084127232";
734
+ var SENSITIVE_PATTERNS = [/ud_[a-zA-Z0-9_-]+/g, /pk_[a-zA-Z0-9_-]+/g, /[^\s@]+@[^\s@]+\.[^\s@]+/g];
735
+ function scrubSensitive(str) {
736
+ let result = str;
737
+ for (const pattern of SENSITIVE_PATTERNS) {
738
+ result = result.replace(pattern, "[REDACTED]");
739
+ }
740
+ return result;
741
+ }
742
+ function initSentry() {
743
+ Sentry.init({
744
+ dsn: SENTRY_DSN,
745
+ environment: process.env.NODE_ENV || "production",
746
+ release: `userdispatch-cli@${process.env.npm_package_version || "unknown"}`,
747
+ beforeSend(event) {
748
+ if (event.exception?.values) {
749
+ for (const ex of event.exception.values) {
750
+ if (ex.value) ex.value = scrubSensitive(ex.value);
751
+ }
752
+ }
753
+ if (event.breadcrumbs) {
754
+ for (const crumb of event.breadcrumbs) {
755
+ if (crumb.message) crumb.message = scrubSensitive(crumb.message);
756
+ if (crumb.data) {
757
+ for (const [key, val] of Object.entries(crumb.data)) {
758
+ if (typeof val === "string") {
759
+ crumb.data[key] = scrubSensitive(val);
760
+ }
761
+ }
762
+ }
763
+ }
764
+ }
765
+ return event;
766
+ }
767
+ });
768
+ }
769
+ function captureCliError(err, context) {
770
+ const safeContext = { ...context };
771
+ delete safeContext.token;
772
+ delete safeContext.apiKey;
773
+ delete safeContext.email;
774
+ Sentry.withScope((scope) => {
775
+ scope.setTag("cli_step", String(safeContext.step || "unknown"));
776
+ scope.setContext("cli", {
777
+ ...safeContext,
778
+ platform: process.platform,
779
+ arch: process.arch
780
+ });
781
+ if (err instanceof Error) {
782
+ Sentry.captureException(err);
783
+ } else {
784
+ Sentry.captureMessage(String(err), "error");
785
+ }
786
+ });
787
+ }
788
+
618
789
  // src/commands/init.ts
619
790
  async function initCommand(flags2) {
791
+ initSentry();
620
792
  intro("UserDispatch Setup");
621
- const auth = await authStep(flags2);
622
- const setup = await setupStep(auth, flags2);
623
- const modifiedFiles = [];
624
- let widget;
793
+ log7.info("Collect user feedback. Let your AI agent handle it.");
794
+ log7.info("This wizard will authenticate you, create your app, install the widget, and connect your coding agent.\n");
795
+ let currentStep = "init";
625
796
  try {
626
- widget = await widgetStep(setup.app.apiKey, flags2);
627
- if (widget.file) modifiedFiles.push(widget.file);
628
- } catch (err) {
629
- log7.warn(`Widget setup encountered an issue: ${err instanceof Error ? err.message : String(err)}`);
630
- widget = {
631
- installed: false,
632
- injected: false,
633
- framework: "unknown",
634
- packageManager: "npm"
635
- };
636
- }
637
- let mcp;
638
- try {
639
- mcp = await mcpStep(auth.token, flags2);
640
- for (const agent of mcp.configured) {
641
- modifiedFiles.push(agent.configPath);
797
+ currentStep = "auth";
798
+ const auth = await authStep(flags2);
799
+ currentStep = "setup";
800
+ const setup = await setupStep(auth, flags2);
801
+ const modifiedFiles = [];
802
+ currentStep = "widget";
803
+ let widget;
804
+ try {
805
+ widget = await widgetStep(setup.app.apiKey, flags2);
806
+ if (widget.file) modifiedFiles.push(widget.file);
807
+ } catch (err) {
808
+ captureCliError(err, { step: "widget", framework: flags2.framework, nodeVersion: process.version });
809
+ log7.warn(`Widget setup encountered an issue: ${err instanceof Error ? err.message : String(err)}`);
810
+ widget = {
811
+ installed: false,
812
+ injected: false,
813
+ framework: "unknown",
814
+ packageManager: "npm"
815
+ };
816
+ }
817
+ currentStep = "mcp";
818
+ let mcp;
819
+ try {
820
+ mcp = await mcpStep(auth.token, flags2);
821
+ for (const agent of mcp.configured) {
822
+ modifiedFiles.push(agent.configPath);
823
+ }
824
+ } catch (err) {
825
+ captureCliError(err, { step: "mcp", nodeVersion: process.version });
826
+ log7.warn(`MCP setup encountered an issue: ${err instanceof Error ? err.message : String(err)}`);
827
+ mcp = { configured: [], skipped: true };
642
828
  }
829
+ currentStep = "verify";
830
+ let verified = false;
831
+ try {
832
+ verified = await verifyStep(setup.app.apiKey);
833
+ } catch (err) {
834
+ captureCliError(err, { step: "verify", nodeVersion: process.version });
835
+ log7.warn(`Verification encountered an issue: ${err instanceof Error ? err.message : String(err)}`);
836
+ }
837
+ currentStep = "summary";
838
+ summaryStep({
839
+ auth,
840
+ setup,
841
+ widget,
842
+ mcp,
843
+ verified,
844
+ modifiedFiles
845
+ });
643
846
  } catch (err) {
644
- log7.warn(`MCP setup encountered an issue: ${err instanceof Error ? err.message : String(err)}`);
645
- mcp = { configured: [], skipped: true };
847
+ captureCliError(err, { step: currentStep, nodeVersion: process.version });
848
+ throw err;
646
849
  }
647
- let verified = false;
648
- try {
649
- verified = await verifyStep(setup.app.apiKey);
650
- } catch (err) {
651
- log7.warn(`Verification encountered an issue: ${err instanceof Error ? err.message : String(err)}`);
652
- }
653
- summaryStep({
654
- auth,
655
- setup,
656
- widget,
657
- mcp,
658
- verified,
659
- modifiedFiles
660
- });
661
850
  }
662
851
 
663
852
  // src/lib/args.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "userdispatch",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "CLI installer for UserDispatch feedback widget + MCP server",
5
5
  "bin": {
6
6
  "userdispatch": "dist/index.js"
@@ -19,7 +19,8 @@
19
19
  "test:watch": "vitest"
20
20
  },
21
21
  "dependencies": {
22
- "@clack/prompts": "^0.9.0"
22
+ "@clack/prompts": "^0.9.0",
23
+ "@sentry/node": "^9.0.0"
23
24
  },
24
25
  "devDependencies": {
25
26
  "tsup": "^8.0.0",