userdispatch 0.2.0 → 0.3.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/chunk-JSBRDJBE.js +30 -0
- package/dist/index.js +26 -851
- package/dist/init-DQZBIX7E.js +1007 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,853 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
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
|
-
});
|
|
100
|
-
|
|
101
|
-
// src/index.ts
|
|
102
|
-
import { intro as intro2, outro as outro2 } from "@clack/prompts";
|
|
103
|
-
|
|
104
|
-
// src/commands/init.ts
|
|
105
|
-
import { intro, log as log7 } from "@clack/prompts";
|
|
106
|
-
|
|
107
|
-
// src/steps/auth.ts
|
|
108
|
-
var import_picocolors = __toESM(require_picocolors(), 1);
|
|
109
|
-
import { text, spinner, log, isCancel } from "@clack/prompts";
|
|
110
|
-
import { exec } from "child_process";
|
|
111
|
-
import { platform } from "os";
|
|
112
|
-
|
|
113
|
-
// src/lib/config.ts
|
|
114
|
-
var PROD_URL = process.env.USERDISPATCH_API_URL || "https://userdispatch.com";
|
|
115
|
-
var API_BASE = `${PROD_URL}/api`;
|
|
116
|
-
|
|
117
|
-
// src/lib/api.ts
|
|
118
|
-
var ApiError = class extends Error {
|
|
119
|
-
constructor(status, body) {
|
|
120
|
-
super(body.error || `HTTP ${status}`);
|
|
121
|
-
this.status = status;
|
|
122
|
-
this.body = body;
|
|
123
|
-
this.name = "ApiError";
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
async function request(path, options) {
|
|
127
|
-
const url = `${API_BASE}${path}`;
|
|
128
|
-
const res = await fetch(url, options);
|
|
129
|
-
const body = await res.json().catch(() => ({ error: "Invalid JSON response" }));
|
|
130
|
-
if (!res.ok) {
|
|
131
|
-
throw new ApiError(res.status, body);
|
|
132
|
-
}
|
|
133
|
-
return body;
|
|
134
|
-
}
|
|
135
|
-
async function apiGet(path, token) {
|
|
136
|
-
return request(path, {
|
|
137
|
-
method: "GET",
|
|
138
|
-
headers: {
|
|
139
|
-
Authorization: `Bearer ${token}`,
|
|
140
|
-
Accept: "application/json"
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
async function apiPost(path, body, token) {
|
|
145
|
-
return request(path, {
|
|
146
|
-
method: "POST",
|
|
147
|
-
headers: {
|
|
148
|
-
Authorization: `Bearer ${token}`,
|
|
149
|
-
"Content-Type": "application/json",
|
|
150
|
-
Accept: "application/json"
|
|
151
|
-
},
|
|
152
|
-
body: JSON.stringify(body)
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
async function apiSubmission(path, body, apiKey) {
|
|
156
|
-
return request(path, {
|
|
157
|
-
method: "POST",
|
|
158
|
-
headers: {
|
|
159
|
-
"X-API-Key": apiKey,
|
|
160
|
-
"Content-Type": "application/json",
|
|
161
|
-
Accept: "application/json"
|
|
162
|
-
},
|
|
163
|
-
body: JSON.stringify(body)
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// src/steps/auth.ts
|
|
168
|
-
function openBrowser(url) {
|
|
169
|
-
const os = platform();
|
|
170
|
-
const cmd = os === "darwin" ? "open" : os === "win32" ? "start" : "xdg-open";
|
|
171
|
-
exec(`${cmd} "${url}"`, (err) => {
|
|
172
|
-
if (err) {
|
|
173
|
-
log.warn(`Could not open browser. Please visit:
|
|
174
|
-
${url}`);
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
async function validateToken(token) {
|
|
179
|
-
try {
|
|
180
|
-
const res = await apiGet("/cli/auth", token);
|
|
181
|
-
return { email: res.data.email || "", org: res.data.org };
|
|
182
|
-
} catch (err) {
|
|
183
|
-
if (err instanceof ApiError && err.status === 401) {
|
|
184
|
-
throw new Error("Invalid token. Please try again.");
|
|
185
|
-
}
|
|
186
|
-
throw err;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
async function authStep(flags2) {
|
|
190
|
-
const s = spinner();
|
|
191
|
-
let token = flags2.token;
|
|
192
|
-
if (!token) {
|
|
193
|
-
const authUrl = `${PROD_URL}/cli/auth`;
|
|
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");
|
|
196
|
-
log.info("Opening your browser to sign in with Google.");
|
|
197
|
-
log.info("After signing in, copy the token shown on screen and paste it here.");
|
|
198
|
-
openBrowser(authUrl);
|
|
199
|
-
const result = await text({
|
|
200
|
-
message: "Paste your token here (starts with ud_):",
|
|
201
|
-
placeholder: "ud_...",
|
|
202
|
-
validate(value) {
|
|
203
|
-
if (!value.trim()) return "Token is required.";
|
|
204
|
-
if (!value.trim().startsWith("ud_")) return 'Token should start with "ud_".';
|
|
205
|
-
}
|
|
206
|
-
});
|
|
207
|
-
if (isCancel(result)) {
|
|
208
|
-
log.warn("Setup cancelled.");
|
|
209
|
-
process.exit(0);
|
|
210
|
-
}
|
|
211
|
-
token = result.trim();
|
|
212
|
-
}
|
|
213
|
-
s.start("Validating token...");
|
|
214
|
-
try {
|
|
215
|
-
const { email, org } = await validateToken(token);
|
|
216
|
-
s.stop("Token validated.");
|
|
217
|
-
if (email) {
|
|
218
|
-
log.info(`Signed in as ${email}`);
|
|
219
|
-
}
|
|
220
|
-
if (org) {
|
|
221
|
-
log.info(`Organization: ${org.name} (${org.slug})`);
|
|
222
|
-
} else {
|
|
223
|
-
log.info("No organization found \u2014 we'll create one next.");
|
|
224
|
-
}
|
|
225
|
-
return { token, email, org };
|
|
226
|
-
} catch (err) {
|
|
227
|
-
s.stop("Token validation failed.");
|
|
228
|
-
if (err instanceof Error) {
|
|
229
|
-
log.error(err.message);
|
|
230
|
-
}
|
|
231
|
-
process.exit(1);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// src/steps/setup.ts
|
|
236
|
-
var import_picocolors2 = __toESM(require_picocolors(), 1);
|
|
237
|
-
import { text as text2, spinner as spinner2, log as log2, isCancel as isCancel2 } from "@clack/prompts";
|
|
238
|
-
import { basename } from "path";
|
|
239
|
-
function slugify(str) {
|
|
240
|
-
return str.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
241
|
-
}
|
|
242
|
-
async function promptForValue(message, defaultValue, placeholder) {
|
|
243
|
-
const result = await text2({
|
|
244
|
-
message,
|
|
245
|
-
defaultValue,
|
|
246
|
-
placeholder: placeholder || defaultValue,
|
|
247
|
-
validate(value) {
|
|
248
|
-
if (!value.trim() && !defaultValue) return "This field is required.";
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
if (isCancel2(result)) {
|
|
252
|
-
log2.warn("Setup cancelled.");
|
|
253
|
-
process.exit(0);
|
|
254
|
-
}
|
|
255
|
-
return result.trim() || defaultValue;
|
|
256
|
-
}
|
|
257
|
-
async function setupStep(auth, flags2 = {}) {
|
|
258
|
-
const s = spinner2();
|
|
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");
|
|
261
|
-
let orgName;
|
|
262
|
-
let orgSlug;
|
|
263
|
-
if (!auth.org) {
|
|
264
|
-
if (flags2.ci && flags2.org) {
|
|
265
|
-
orgName = flags2.org;
|
|
266
|
-
orgSlug = slugify(flags2.org);
|
|
267
|
-
} else {
|
|
268
|
-
log2.info("This is your team or company name \u2014 shown in the dashboard header.");
|
|
269
|
-
orgName = await promptForValue("Organization name:", "My Organization");
|
|
270
|
-
log2.info("URL-friendly identifier \u2014 your dashboard will be at userdispatch.com/org/<slug>.");
|
|
271
|
-
orgSlug = await promptForValue("Organization slug:", slugify(orgName));
|
|
272
|
-
}
|
|
273
|
-
} else {
|
|
274
|
-
log2.info(`Using organization: ${auth.org.name}`);
|
|
275
|
-
}
|
|
276
|
-
const defaultAppName = basename(process.cwd());
|
|
277
|
-
let appName;
|
|
278
|
-
let appSlug;
|
|
279
|
-
if (flags2.ci && flags2.app) {
|
|
280
|
-
appName = flags2.app;
|
|
281
|
-
appSlug = slugify(flags2.app);
|
|
282
|
-
} else {
|
|
283
|
-
log2.info("The project you're collecting feedback for \u2014 shown in the widget and dashboard.");
|
|
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>.");
|
|
286
|
-
appSlug = await promptForValue("App slug:", slugify(appName));
|
|
287
|
-
}
|
|
288
|
-
s.start("Creating your setup...");
|
|
289
|
-
try {
|
|
290
|
-
const body = {
|
|
291
|
-
appName,
|
|
292
|
-
appSlug
|
|
293
|
-
};
|
|
294
|
-
if (orgName) body.orgName = orgName;
|
|
295
|
-
if (orgSlug) body.orgSlug = orgSlug;
|
|
296
|
-
const res = await apiPost("/cli/setup", body, auth.token);
|
|
297
|
-
s.stop("Setup complete.");
|
|
298
|
-
const { org, app, isNewOrg } = res.data;
|
|
299
|
-
if (isNewOrg) {
|
|
300
|
-
log2.info(`Created organization: ${org.name} (${org.slug})`);
|
|
301
|
-
}
|
|
302
|
-
log2.info(`Created app: ${app.name} (${app.slug})`);
|
|
303
|
-
return { org, app, isNewOrg };
|
|
304
|
-
} catch (err) {
|
|
305
|
-
s.stop("Setup failed.");
|
|
306
|
-
if (err instanceof ApiError) {
|
|
307
|
-
if (err.status === 409) {
|
|
308
|
-
log2.error(`Conflict: ${err.message}. Try a different name or slug.`);
|
|
309
|
-
} else {
|
|
310
|
-
log2.error(err.message);
|
|
311
|
-
}
|
|
312
|
-
} else if (err instanceof Error) {
|
|
313
|
-
log2.error(err.message);
|
|
314
|
-
}
|
|
315
|
-
process.exit(1);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// src/steps/widget.ts
|
|
320
|
-
var import_picocolors3 = __toESM(require_picocolors(), 1);
|
|
321
|
-
import { spinner as spinner3, log as log3, confirm, isCancel as isCancel3 } from "@clack/prompts";
|
|
322
|
-
import { execSync } from "child_process";
|
|
323
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
324
|
-
import { join as join2 } from "path";
|
|
325
|
-
|
|
326
|
-
// src/lib/detect.ts
|
|
327
|
-
import { existsSync, readFileSync } from "fs";
|
|
328
|
-
import { join } from "path";
|
|
329
|
-
import { homedir } from "os";
|
|
330
|
-
function detectPackageManager(cwd = process.cwd()) {
|
|
331
|
-
if (existsSync(join(cwd, "bun.lockb")) || existsSync(join(cwd, "bun.lock"))) return "bun";
|
|
332
|
-
if (existsSync(join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
333
|
-
if (existsSync(join(cwd, "yarn.lock"))) return "yarn";
|
|
334
|
-
return "npm";
|
|
335
|
-
}
|
|
336
|
-
function detectFramework(cwd = process.cwd()) {
|
|
337
|
-
const pkgPath = join(cwd, "package.json");
|
|
338
|
-
let deps = {};
|
|
339
|
-
if (existsSync(pkgPath)) {
|
|
340
|
-
try {
|
|
341
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
342
|
-
deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
343
|
-
} catch {
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
if (deps["next"]) {
|
|
347
|
-
if (existsSync(join(cwd, "app/layout.tsx")) || existsSync(join(cwd, "app/layout.jsx"))) {
|
|
348
|
-
return { framework: "next-app", label: "Next.js (App Router)", layoutFile: join(cwd, "app/layout.tsx") };
|
|
349
|
-
}
|
|
350
|
-
if (existsSync(join(cwd, "src/app/layout.tsx")) || existsSync(join(cwd, "src/app/layout.jsx"))) {
|
|
351
|
-
return { framework: "next-app", label: "Next.js (App Router)", layoutFile: join(cwd, "src/app/layout.tsx") };
|
|
352
|
-
}
|
|
353
|
-
if (existsSync(join(cwd, "pages/_document.tsx")) || existsSync(join(cwd, "pages/_document.jsx"))) {
|
|
354
|
-
return { framework: "next-pages", label: "Next.js (Pages Router)", layoutFile: join(cwd, "pages/_document.tsx") };
|
|
355
|
-
}
|
|
356
|
-
return { framework: "next-app", label: "Next.js", layoutFile: void 0 };
|
|
357
|
-
}
|
|
358
|
-
if (deps["vite"]) {
|
|
359
|
-
return { framework: "vite", label: "Vite", layoutFile: join(cwd, "index.html") };
|
|
360
|
-
}
|
|
361
|
-
if (deps["react-scripts"]) {
|
|
362
|
-
return { framework: "cra", label: "Create React App", layoutFile: join(cwd, "public/index.html") };
|
|
363
|
-
}
|
|
364
|
-
if (deps["nuxt"]) {
|
|
365
|
-
return { framework: "nuxt", label: "Nuxt", layoutFile: join(cwd, "nuxt.config.ts") };
|
|
366
|
-
}
|
|
367
|
-
if (deps["@sveltejs/kit"]) {
|
|
368
|
-
return { framework: "sveltekit", label: "SvelteKit", layoutFile: join(cwd, "src/app.html") };
|
|
369
|
-
}
|
|
370
|
-
if (deps["astro"]) {
|
|
371
|
-
return { framework: "astro", label: "Astro", layoutFile: void 0 };
|
|
372
|
-
}
|
|
373
|
-
if (existsSync(join(cwd, "index.html"))) {
|
|
374
|
-
return { framework: "static", label: "Static HTML", layoutFile: join(cwd, "index.html") };
|
|
375
|
-
}
|
|
376
|
-
return { framework: "unknown", label: "Unknown", layoutFile: void 0 };
|
|
377
|
-
}
|
|
378
|
-
function detectAgents(cwd = process.cwd()) {
|
|
379
|
-
const home = homedir();
|
|
380
|
-
const agents = [
|
|
381
|
-
{
|
|
382
|
-
name: "Claude Code",
|
|
383
|
-
id: "claude-code",
|
|
384
|
-
configPath: join(cwd, ".mcp.json"),
|
|
385
|
-
detected: true
|
|
386
|
-
// always offer Claude Code
|
|
387
|
-
},
|
|
388
|
-
{
|
|
389
|
-
name: "Cursor",
|
|
390
|
-
id: "cursor",
|
|
391
|
-
configPath: join(cwd, ".cursor/mcp.json"),
|
|
392
|
-
detected: existsSync(join(cwd, ".cursor"))
|
|
393
|
-
},
|
|
394
|
-
{
|
|
395
|
-
name: "Windsurf",
|
|
396
|
-
id: "windsurf",
|
|
397
|
-
configPath: join(home, ".codeium/windsurf/mcp_config.json"),
|
|
398
|
-
detected: existsSync(join(home, ".codeium/windsurf"))
|
|
399
|
-
},
|
|
400
|
-
{
|
|
401
|
-
name: "Claude Desktop",
|
|
402
|
-
id: "claude-desktop",
|
|
403
|
-
configPath: join(home, "Library/Application Support/Claude/claude_desktop_config.json"),
|
|
404
|
-
detected: existsSync(join(home, "Library/Application Support/Claude"))
|
|
405
|
-
},
|
|
406
|
-
{
|
|
407
|
-
name: "VS Code Copilot",
|
|
408
|
-
id: "vscode",
|
|
409
|
-
configPath: join(cwd, ".vscode/mcp.json"),
|
|
410
|
-
detected: existsSync(join(cwd, ".vscode"))
|
|
411
|
-
}
|
|
412
|
-
];
|
|
413
|
-
return agents;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// src/steps/widget.ts
|
|
417
|
-
var WIDGET_URL = `${PROD_URL}/widget.js`;
|
|
418
|
-
function installCommand(pm, pkg) {
|
|
419
|
-
switch (pm) {
|
|
420
|
-
case "bun":
|
|
421
|
-
return `bun add ${pkg}`;
|
|
422
|
-
case "pnpm":
|
|
423
|
-
return `pnpm add ${pkg}`;
|
|
424
|
-
case "yarn":
|
|
425
|
-
return `yarn add ${pkg}`;
|
|
426
|
-
default:
|
|
427
|
-
return `npm install ${pkg}`;
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
function isAlreadyInstalled(cwd) {
|
|
431
|
-
const pkgPath = join2(cwd, "package.json");
|
|
432
|
-
if (!existsSync2(pkgPath)) return false;
|
|
433
|
-
try {
|
|
434
|
-
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
435
|
-
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
436
|
-
return !!allDeps["@userdispatch/sdk"];
|
|
437
|
-
} catch {
|
|
438
|
-
return false;
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
function fileContainsWidget(filePath) {
|
|
442
|
-
if (!existsSync2(filePath)) return false;
|
|
443
|
-
const content = readFileSync2(filePath, "utf-8");
|
|
444
|
-
return content.includes("userdispatch.com/widget.js") || content.includes("userdispatch/widget");
|
|
445
|
-
}
|
|
446
|
-
function injectNextAppRouter(filePath, apiKey) {
|
|
447
|
-
if (!existsSync2(filePath)) return false;
|
|
448
|
-
let content = readFileSync2(filePath, "utf-8");
|
|
449
|
-
if (fileContainsWidget(filePath)) {
|
|
450
|
-
log3.info("Widget already present in layout file \u2014 skipping injection.");
|
|
451
|
-
return false;
|
|
452
|
-
}
|
|
453
|
-
if (!content.includes("from 'next/script'") && !content.includes('from "next/script"')) {
|
|
454
|
-
const lastImportIndex = content.lastIndexOf("import ");
|
|
455
|
-
if (lastImportIndex !== -1) {
|
|
456
|
-
const endOfImport = content.indexOf("\n", lastImportIndex);
|
|
457
|
-
const importLine = `
|
|
458
|
-
import Script from "next/script";`;
|
|
459
|
-
content = content.slice(0, endOfImport + 1) + importLine + content.slice(endOfImport + 1);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
const scriptTag = ` <Script src="${WIDGET_URL}" data-api-key="${apiKey}" strategy="afterInteractive" />`;
|
|
463
|
-
const bodyCloseIndex = content.lastIndexOf("</body>");
|
|
464
|
-
if (bodyCloseIndex === -1) {
|
|
465
|
-
log3.warn("Could not find </body> tag in layout file.");
|
|
466
|
-
return false;
|
|
467
|
-
}
|
|
468
|
-
content = content.slice(0, bodyCloseIndex) + scriptTag + "\n" + content.slice(bodyCloseIndex);
|
|
469
|
-
writeFileSync(filePath, content, "utf-8");
|
|
470
|
-
return true;
|
|
471
|
-
}
|
|
472
|
-
function injectHtmlFile(filePath, apiKey) {
|
|
473
|
-
if (!existsSync2(filePath)) return false;
|
|
474
|
-
let content = readFileSync2(filePath, "utf-8");
|
|
475
|
-
if (fileContainsWidget(filePath)) {
|
|
476
|
-
log3.info("Widget already present \u2014 skipping injection.");
|
|
477
|
-
return false;
|
|
478
|
-
}
|
|
479
|
-
const scriptTag = ` <script src="${WIDGET_URL}" data-api-key="${apiKey}" defer></script>`;
|
|
480
|
-
const bodyCloseIndex = content.lastIndexOf("</body>");
|
|
481
|
-
if (bodyCloseIndex === -1) {
|
|
482
|
-
log3.warn("Could not find </body> tag in file.");
|
|
483
|
-
return false;
|
|
484
|
-
}
|
|
485
|
-
content = content.slice(0, bodyCloseIndex) + scriptTag + "\n" + content.slice(bodyCloseIndex);
|
|
486
|
-
writeFileSync(filePath, content, "utf-8");
|
|
487
|
-
return true;
|
|
488
|
-
}
|
|
489
|
-
async function widgetStep(apiKey, flags2 = {}) {
|
|
490
|
-
const cwd = process.cwd();
|
|
491
|
-
const s = spinner3();
|
|
492
|
-
const pm = detectPackageManager(cwd);
|
|
493
|
-
const detection = detectFramework(cwd);
|
|
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");
|
|
496
|
-
log3.info(`Detected framework: ${detection.label}`);
|
|
497
|
-
let installed = false;
|
|
498
|
-
if (!isAlreadyInstalled(cwd)) {
|
|
499
|
-
let shouldInstall;
|
|
500
|
-
if (flags2.ci) {
|
|
501
|
-
shouldInstall = true;
|
|
502
|
-
} else {
|
|
503
|
-
shouldInstall = await confirm({
|
|
504
|
-
message: "Install @userdispatch/sdk? (TypeScript SDK for sending feedback from your code)",
|
|
505
|
-
initialValue: true
|
|
506
|
-
});
|
|
507
|
-
if (isCancel3(shouldInstall)) {
|
|
508
|
-
log3.warn("Setup cancelled.");
|
|
509
|
-
process.exit(0);
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
if (shouldInstall) {
|
|
513
|
-
s.start(`Installing @userdispatch/sdk via ${pm}...`);
|
|
514
|
-
try {
|
|
515
|
-
execSync(installCommand(pm, "@userdispatch/sdk"), {
|
|
516
|
-
cwd,
|
|
517
|
-
stdio: "pipe"
|
|
518
|
-
});
|
|
519
|
-
s.stop("SDK installed.");
|
|
520
|
-
installed = true;
|
|
521
|
-
} catch {
|
|
522
|
-
s.stop("SDK installation failed \u2014 you can install it manually later.");
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
} else {
|
|
526
|
-
log3.info("@userdispatch/sdk already installed.");
|
|
527
|
-
installed = true;
|
|
528
|
-
}
|
|
529
|
-
let injected = false;
|
|
530
|
-
const { framework, layoutFile } = detection;
|
|
531
|
-
if (!layoutFile || !existsSync2(layoutFile)) {
|
|
532
|
-
if (framework === "unknown") {
|
|
533
|
-
log3.info("Could not detect framework. Add the widget manually:");
|
|
534
|
-
log3.info(` <script src="${WIDGET_URL}" data-api-key="${apiKey}" defer></script>`);
|
|
535
|
-
} else {
|
|
536
|
-
log3.info(`No layout file found for ${detection.label}. Add the widget manually.`);
|
|
537
|
-
}
|
|
538
|
-
return { installed, injected: false, framework, packageManager: pm };
|
|
539
|
-
}
|
|
540
|
-
if (framework === "next-app") {
|
|
541
|
-
injected = injectNextAppRouter(layoutFile, apiKey);
|
|
542
|
-
} else if (["vite", "cra", "sveltekit", "static"].includes(framework)) {
|
|
543
|
-
injected = injectHtmlFile(layoutFile, apiKey);
|
|
544
|
-
} else if (framework === "nuxt") {
|
|
545
|
-
log3.info("For Nuxt, add the widget script to your nuxt.config.ts:");
|
|
546
|
-
log3.info(` app: { head: { script: [{ src: "${WIDGET_URL}", "data-api-key": "${apiKey}", defer: true }] } }`);
|
|
547
|
-
} else if (framework === "next-pages") {
|
|
548
|
-
injected = injectHtmlFile(layoutFile, apiKey);
|
|
549
|
-
} else {
|
|
550
|
-
log3.info("Add the widget manually:");
|
|
551
|
-
log3.info(` <script src="${WIDGET_URL}" data-api-key="${apiKey}" defer></script>`);
|
|
552
|
-
}
|
|
553
|
-
if (injected) {
|
|
554
|
-
log3.success(`Widget injected into ${layoutFile}`);
|
|
555
|
-
}
|
|
556
|
-
return { installed, injected, framework, file: injected ? layoutFile : void 0, packageManager: pm };
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
// src/steps/mcp.ts
|
|
560
|
-
var import_picocolors4 = __toESM(require_picocolors(), 1);
|
|
561
|
-
import { multiselect, spinner as spinner4, log as log4, isCancel as isCancel4 } from "@clack/prompts";
|
|
562
|
-
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync } from "fs";
|
|
563
|
-
import { dirname, relative } from "path";
|
|
564
|
-
var MCP_URL = `${PROD_URL}/api/mcp`;
|
|
565
|
-
function readJsonFile(filePath) {
|
|
566
|
-
if (!existsSync3(filePath)) return {};
|
|
567
|
-
try {
|
|
568
|
-
return JSON.parse(readFileSync3(filePath, "utf-8"));
|
|
569
|
-
} catch {
|
|
570
|
-
return {};
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
function writeJsonFile(filePath, data) {
|
|
574
|
-
const dir = dirname(filePath);
|
|
575
|
-
if (!existsSync3(dir)) {
|
|
576
|
-
mkdirSync(dir, { recursive: true });
|
|
577
|
-
}
|
|
578
|
-
writeFileSync2(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
579
|
-
}
|
|
580
|
-
function addToGitignore(cwd, entry) {
|
|
581
|
-
const gitignorePath = `${cwd}/.gitignore`;
|
|
582
|
-
if (!existsSync3(gitignorePath)) return;
|
|
583
|
-
const content = readFileSync3(gitignorePath, "utf-8");
|
|
584
|
-
if (content.includes(entry)) return;
|
|
585
|
-
const newline = content.endsWith("\n") ? "" : "\n";
|
|
586
|
-
writeFileSync2(gitignorePath, content + newline + entry + "\n", "utf-8");
|
|
587
|
-
}
|
|
588
|
-
function configureMcp(agent, token, cwd) {
|
|
589
|
-
const config = readJsonFile(agent.configPath);
|
|
590
|
-
const mcpServers = config.mcpServers ?? {};
|
|
591
|
-
mcpServers["userdispatch"] = {
|
|
592
|
-
url: MCP_URL,
|
|
593
|
-
headers: {
|
|
594
|
-
Authorization: `Bearer ${token}`
|
|
595
|
-
}
|
|
596
|
-
};
|
|
597
|
-
config.mcpServers = mcpServers;
|
|
598
|
-
writeJsonFile(agent.configPath, config);
|
|
599
|
-
const relPath = relative(cwd, agent.configPath);
|
|
600
|
-
if (!relPath.startsWith("..") && !relPath.startsWith("/")) {
|
|
601
|
-
addToGitignore(cwd, relPath);
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
async function mcpStep(token, flags2 = {}) {
|
|
605
|
-
const cwd = process.cwd();
|
|
606
|
-
const agents = detectAgents(cwd);
|
|
607
|
-
const detected = agents.filter((a) => a.detected);
|
|
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");
|
|
610
|
-
if (detected.length === 0) {
|
|
611
|
-
log4.info("No coding agents detected. You can configure MCP manually later.");
|
|
612
|
-
return { configured: [], skipped: true };
|
|
613
|
-
}
|
|
614
|
-
let selected;
|
|
615
|
-
if (flags2.ci) {
|
|
616
|
-
selected = detected;
|
|
617
|
-
log4.info(`Configuring MCP for ${detected.map((a) => a.name).join(", ")}...`);
|
|
618
|
-
} else if (detected.length === 1) {
|
|
619
|
-
selected = detected;
|
|
620
|
-
log4.info(`Configuring MCP for ${detected[0].name}...`);
|
|
621
|
-
} else {
|
|
622
|
-
const result = await multiselect({
|
|
623
|
-
message: "Which coding agents should we configure?",
|
|
624
|
-
options: detected.map((a) => ({
|
|
625
|
-
value: a.id,
|
|
626
|
-
label: a.name,
|
|
627
|
-
hint: a.configPath
|
|
628
|
-
})),
|
|
629
|
-
initialValues: detected.map((a) => a.id)
|
|
630
|
-
});
|
|
631
|
-
if (isCancel4(result)) {
|
|
632
|
-
log4.warn("Setup cancelled.");
|
|
633
|
-
process.exit(0);
|
|
634
|
-
}
|
|
635
|
-
selected = detected.filter((a) => result.includes(a.id));
|
|
636
|
-
}
|
|
637
|
-
if (selected.length === 0) {
|
|
638
|
-
log4.info("Skipped MCP configuration.");
|
|
639
|
-
return { configured: [], skipped: true };
|
|
640
|
-
}
|
|
641
|
-
const s = spinner4();
|
|
642
|
-
s.start("Configuring MCP servers...");
|
|
643
|
-
for (const agent of selected) {
|
|
644
|
-
configureMcp(agent, token, cwd);
|
|
645
|
-
}
|
|
646
|
-
s.stop("MCP servers configured.");
|
|
647
|
-
for (const agent of selected) {
|
|
648
|
-
log4.info(` ${agent.name}: ${agent.configPath}`);
|
|
649
|
-
}
|
|
650
|
-
return { configured: selected, skipped: false };
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
// src/steps/verify.ts
|
|
654
|
-
var import_picocolors5 = __toESM(require_picocolors(), 1);
|
|
655
|
-
import { spinner as spinner5, log as log5 } from "@clack/prompts";
|
|
656
|
-
async function verifyStep(apiKey) {
|
|
657
|
-
const s = spinner5();
|
|
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");
|
|
660
|
-
s.start("Sending a test submission to your dashboard...");
|
|
661
|
-
try {
|
|
662
|
-
await apiSubmission(
|
|
663
|
-
"/v1/submissions",
|
|
664
|
-
{
|
|
665
|
-
type: "feedback",
|
|
666
|
-
subject: "Setup complete",
|
|
667
|
-
message: "Test submission from npx userdispatch init.",
|
|
668
|
-
metadata: { source: "cli-init", test: true }
|
|
669
|
-
},
|
|
670
|
-
apiKey
|
|
671
|
-
);
|
|
672
|
-
s.stop("Test submission sent \u2014 check your dashboard to see it.");
|
|
673
|
-
return true;
|
|
674
|
-
} catch (err) {
|
|
675
|
-
s.stop("Test submission failed.");
|
|
676
|
-
if (err instanceof ApiError) {
|
|
677
|
-
log5.warn(`Could not send test submission: ${err.message}`);
|
|
678
|
-
} else if (err instanceof Error) {
|
|
679
|
-
log5.warn(`Could not send test submission: ${err.message}`);
|
|
680
|
-
}
|
|
681
|
-
return false;
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
// src/steps/summary.ts
|
|
686
|
-
var import_picocolors6 = __toESM(require_picocolors(), 1);
|
|
687
|
-
import { note, outro, log as log6 } from "@clack/prompts";
|
|
688
|
-
function summaryStep(result) {
|
|
689
|
-
const { setup, widget, mcp, verified, modifiedFiles } = result;
|
|
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";
|
|
692
|
-
const lines = [];
|
|
693
|
-
lines.push(`Organization: ${setup.org.name} (${setup.org.slug})`);
|
|
694
|
-
lines.push(`App: ${setup.app.name} (${setup.app.slug})`);
|
|
695
|
-
lines.push(`API Key: ${maskedKey}`);
|
|
696
|
-
lines.push("");
|
|
697
|
-
if (widget.installed) {
|
|
698
|
-
lines.push(`SDK: installed via ${widget.packageManager}`);
|
|
699
|
-
}
|
|
700
|
-
if (widget.injected && widget.file) {
|
|
701
|
-
lines.push(`Widget: injected into ${widget.file}`);
|
|
702
|
-
} else if (!widget.injected) {
|
|
703
|
-
lines.push(`Widget: manual setup needed`);
|
|
704
|
-
}
|
|
705
|
-
if (mcp.configured.length > 0) {
|
|
706
|
-
const names = mcp.configured.map((a) => a.name).join(", ");
|
|
707
|
-
lines.push(`MCP: configured for ${names}`);
|
|
708
|
-
}
|
|
709
|
-
lines.push(`Test: ${verified ? "passed" : "skipped (check dashboard)"}`);
|
|
710
|
-
if (modifiedFiles.length > 0) {
|
|
711
|
-
lines.push("");
|
|
712
|
-
lines.push("Files modified:");
|
|
713
|
-
for (const f of modifiedFiles) {
|
|
714
|
-
lines.push(` ${f}`);
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
note(lines.join("\n"), "Setup Summary");
|
|
718
|
-
const nextSteps = [
|
|
719
|
-
`Dashboard: ${PROD_URL}/org/${setup.org.slug}/dashboard`,
|
|
720
|
-
`Submissions: ${PROD_URL}/org/${setup.org.slug}/submissions`,
|
|
721
|
-
"",
|
|
722
|
-
"Next steps:",
|
|
723
|
-
" 1. Start your dev server \u2014 the widget appears bottom-right",
|
|
724
|
-
" 2. Submit feedback through the widget",
|
|
725
|
-
' 3. Open Claude Code and try: "Show me my recent submissions"'
|
|
726
|
-
].join("\n");
|
|
727
|
-
note(nextSteps, "What's Next");
|
|
728
|
-
outro("You're all set! Happy building.");
|
|
729
|
-
}
|
|
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
|
-
|
|
789
|
-
// src/commands/init.ts
|
|
790
|
-
async function initCommand(flags2) {
|
|
791
|
-
initSentry();
|
|
792
|
-
intro("UserDispatch Setup");
|
|
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";
|
|
796
|
-
try {
|
|
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 };
|
|
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
|
-
});
|
|
846
|
-
} catch (err) {
|
|
847
|
-
captureCliError(err, { step: currentStep, nodeVersion: process.version });
|
|
848
|
-
throw err;
|
|
849
|
-
}
|
|
850
|
-
}
|
|
2
|
+
import "./chunk-JSBRDJBE.js";
|
|
851
3
|
|
|
852
4
|
// src/lib/args.ts
|
|
853
5
|
function parseArgs(argv) {
|
|
@@ -889,14 +41,37 @@ function parseArgs(argv) {
|
|
|
889
41
|
return flags2;
|
|
890
42
|
}
|
|
891
43
|
|
|
44
|
+
// src/lib/tty.ts
|
|
45
|
+
function isInteractive() {
|
|
46
|
+
return Boolean(process.stdin.isTTY);
|
|
47
|
+
}
|
|
48
|
+
|
|
892
49
|
// src/index.ts
|
|
893
50
|
var args = process.argv.slice(2);
|
|
894
51
|
var command = args.find((a) => !a.startsWith("--"));
|
|
895
52
|
var flags = parseArgs(args);
|
|
53
|
+
if (!isInteractive() && !flags.ci) {
|
|
54
|
+
if (flags.token) {
|
|
55
|
+
flags.ci = true;
|
|
56
|
+
} else {
|
|
57
|
+
console.log(
|
|
58
|
+
`UserDispatch CLI requires an interactive terminal.
|
|
59
|
+
|
|
60
|
+
Running from a coding agent? Use non-interactive mode:
|
|
61
|
+
|
|
62
|
+
npx userdispatch init --token <TOKEN> --app <app-name>
|
|
63
|
+
|
|
64
|
+
Get your token at: https://userdispatch.com/cli/auth`
|
|
65
|
+
);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
var { intro, outro } = await import("@clack/prompts");
|
|
896
70
|
if (command === "init" || !command) {
|
|
71
|
+
const { initCommand } = await import("./init-DQZBIX7E.js");
|
|
897
72
|
await initCommand(flags);
|
|
898
73
|
} else {
|
|
899
|
-
|
|
900
|
-
|
|
74
|
+
intro("UserDispatch");
|
|
75
|
+
outro(`Unknown command: ${command}. Use "userdispatch init" to get started.`);
|
|
901
76
|
process.exit(1);
|
|
902
77
|
}
|