@userreason/wizard 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.
@@ -0,0 +1,42 @@
1
+ import { createRequire as createWizardRequire } from 'node:module'; import { fileURLToPath as wizardFileURLToPath } from 'node:url'; import { dirname as wizardDirname } from 'node:path'; const require = createWizardRequire(import.meta.url); const __filename = wizardFileURLToPath(import.meta.url); const __dirname = wizardDirname(__filename);
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 __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
9
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
10
+ }) : x)(function(x) {
11
+ if (typeof require !== "undefined") return require.apply(this, arguments);
12
+ throw Error('Dynamic require of "' + x + '" is not supported');
13
+ });
14
+ var __commonJS = (cb, mod) => function __require2() {
15
+ try {
16
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
17
+ } catch (e) {
18
+ throw mod = 0, e;
19
+ }
20
+ };
21
+ var __copyProps = (to, from, except, desc) => {
22
+ if (from && typeof from === "object" || typeof from === "function") {
23
+ for (let key of __getOwnPropNames(from))
24
+ if (!__hasOwnProp.call(to, key) && key !== except)
25
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
26
+ }
27
+ return to;
28
+ };
29
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
30
+ // If the importer is in node compatibility mode or this is not an ESM
31
+ // file that has been converted to a CommonJS file using a Babel-
32
+ // compatible transform (i.e. "__esModule" has not been set), then set
33
+ // "default" to the CommonJS "module.exports" for node compatibility.
34
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
35
+ mod
36
+ ));
37
+
38
+ export {
39
+ __require,
40
+ __commonJS,
41
+ __toESM
42
+ };
@@ -0,0 +1,280 @@
1
+ import { createRequire as createWizardRequire } from 'node:module'; import { fileURLToPath as wizardFileURLToPath } from 'node:url'; import { dirname as wizardDirname } from 'node:path'; const require = createWizardRequire(import.meta.url); const __filename = wizardFileURLToPath(import.meta.url); const __dirname = wizardDirname(__filename);
2
+ import {
3
+ configurePackageRegistry
4
+ } from "./chunk-OEGNGGZ2.js";
5
+ import {
6
+ packageProfile
7
+ } from "./chunk-GGARVSPT.js";
8
+
9
+ // src/project.ts
10
+ import { lstat, readFile, writeFile, access, mkdtemp, realpath, rm } from "node:fs/promises";
11
+ import { join, resolve, dirname } from "node:path";
12
+ import { spawn, execFile } from "node:child_process";
13
+ import { promisify } from "node:util";
14
+ var exists = async (path) => {
15
+ try {
16
+ await access(path);
17
+ return true;
18
+ } catch {
19
+ return false;
20
+ }
21
+ };
22
+ async function inspectProject(directory, framework) {
23
+ directory = resolve(directory);
24
+ const metadata = await lstat(directory);
25
+ if (!metadata.isDirectory()) throw new Error("Project directory must be an existing directory");
26
+ const text = await readFile(join(directory, "package.json"), "utf8");
27
+ if (text.length > 1e6) throw new Error("Project package.json is too large");
28
+ const data = JSON.parse(text);
29
+ if (!data || typeof data !== "object" || Array.isArray(data))
30
+ throw new Error("Invalid project package.json");
31
+ const dependencies = { ...data.dependencies, ...data.devDependencies };
32
+ const inferred = dependencies.next ? "next" : dependencies.react ? "react" : dependencies.vue ? "vue" : "vanilla";
33
+ const packageManager = typeof data.packageManager === "string" && /^(pnpm|yarn)@/.test(data.packageManager) ? data.packageManager.split("@")[0] : await exists(join(directory, "pnpm-lock.yaml")) ? "pnpm" : await exists(join(directory, "yarn.lock")) ? "yarn" : "npm";
34
+ const yarnMajor = packageManager === "yarn" ? typeof data.packageManager === "string" && /^yarn@\d+/.test(data.packageManager) ? Number(data.packageManager.slice(5).split(".")[0]) : await exists(join(directory, ".yarnrc.yml")) ? 4 : 1 : void 0;
35
+ const sourceDirectory = await exists(join(directory, "src")) ? join(directory, "src") : directory;
36
+ let nextVersion = dependencies.next;
37
+ if (inferred === "next") {
38
+ try {
39
+ nextVersion = JSON.parse(await readFile(join(directory, "node_modules/next/package.json"), "utf8")).version;
40
+ } catch {
41
+ }
42
+ }
43
+ const version = typeof nextVersion === "string" ? /^[~^]?(\d+)(?:\.(\d+))?/.exec(nextVersion) : null;
44
+ return {
45
+ directory,
46
+ framework: framework ?? inferred,
47
+ sourceDirectory,
48
+ packageManager,
49
+ yarnMajor,
50
+ name: typeof data.name === "string" && data.name ? data.name : "My site",
51
+ vite: Boolean(dependencies.vite),
52
+ devScript: data.scripts && typeof data.scripts === "object" ? typeof data.scripts.dev === "string" ? "dev" : typeof data.scripts.start === "string" ? "start" : void 0 : void 0,
53
+ nextInstrumentation: Boolean(version && (Number(version[1]) > 15 || Number(version[1]) === 15 && Number(version[2]) >= 3)),
54
+ typescript: Boolean(dependencies.typescript) || await exists(join(directory, "tsconfig.json")) || await exists(join(sourceDirectory, "userreason.generated.tsx")) || await exists(join(sourceDirectory, "userreason.generated.ts"))
55
+ };
56
+ }
57
+ var DEFAULT_API = "https://api.userreason.com";
58
+ function publicOptions(config) {
59
+ return { siteId: config.siteId, ...config.apiBaseUrl !== DEFAULT_API ? { apiBaseUrl: config.apiBaseUrl } : {} };
60
+ }
61
+ function optionsSource(config) {
62
+ return `{
63
+ ${Object.entries(publicOptions(config)).map(([key, value]) => ` ${key}: ${JSON.stringify(value)},`).join("\n")}
64
+ }`;
65
+ }
66
+ function initializationSource(config) {
67
+ return `UserReason.init(${optionsSource(config)});
68
+ `;
69
+ }
70
+ function integrationSource(framework, config, typescript = true, legacyNext = false) {
71
+ const options = optionsSource(config);
72
+ const base = `// Generated by the UserReason wizard. Contains public SDK configuration only.
73
+ `;
74
+ if (framework === "next" && legacyNext)
75
+ return {
76
+ filename: `userreason.client.${typescript ? "tsx" : "jsx"}`,
77
+ content: `'use client';
78
+ ${base}import { useEffect, useLayoutEffect } from 'react';
79
+ import UserReason from '@userreason/js';
80
+
81
+ const useBrowserLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;
82
+
83
+ export function UserReasonInit() {
84
+ useBrowserLayoutEffect(() => {
85
+ UserReason.init(${options});
86
+ return () => UserReason.destroy();
87
+ }, []);
88
+ return null;
89
+ }
90
+ `,
91
+ instruction: framework === "next" ? "Import UserReasonInit from the generated client file and render <UserReasonInit /> once inside <body> or your Pages Router root." : "Initialize UserReason once before mounting your application."
92
+ };
93
+ return {
94
+ filename: `instrumentation-client.${typescript ? "ts" : "js"}`,
95
+ content: `${base}import UserReason from '@userreason/js';
96
+
97
+ ${initializationSource(config)}`,
98
+ instruction: `Add this initialization to ${framework === "next" ? "instrumentation-client.ts/js for Next.js 15.3+, or a browser-only entry for older versions" : "your browser entry before mounting the app"}:
99
+
100
+ import UserReason from '@userreason/js';
101
+
102
+ ${initializationSource(config)}`
103
+ };
104
+ }
105
+ async function writeGenerated(path, content, force) {
106
+ try {
107
+ await writeFile(path, content, { flag: "wx", mode: 420 });
108
+ return;
109
+ } catch (error) {
110
+ if (error.code !== "EEXIST") throw error;
111
+ }
112
+ const metadata = await lstat(path);
113
+ if (!metadata.isFile() || metadata.isSymbolicLink())
114
+ throw new Error(`Refusing to overwrite a non-file: ${path}`);
115
+ if (await readFile(path, "utf8") === content) return;
116
+ if (!force)
117
+ throw new Error(
118
+ `Existing file differs: ${path}. Review it and rerun with --force to replace it.`
119
+ );
120
+ await writeFile(path, content, { mode: 420 });
121
+ }
122
+ async function writeIntegration(project, config, force = false, legacyNext = false) {
123
+ const helper = integrationSource(project.framework, config, project.typescript, legacyNext), helperPath = join(project.sourceDirectory, helper.filename);
124
+ const source = await lstat(dirname(helperPath));
125
+ if (!source.isDirectory() || source.isSymbolicLink())
126
+ throw new Error("Source directory must be a real directory inside the project");
127
+ if (legacyNext) await writeGenerated(helperPath, helper.content, force);
128
+ return { files: legacyNext ? [helperPath] : [], instruction: helper.instruction };
129
+ }
130
+ async function installedVersion(project, name) {
131
+ if (project.packageManager === "yarn" && (project.yarnMajor ?? 1) >= 2 && await exists(join(project.directory, ".pnp.cjs"))) {
132
+ const script = "const p=require('pnpapi').resolveToUnqualified(process.argv[1],process.cwd()+'/'); console.log(JSON.parse(require('node:fs').readFileSync(require('node:path').join(p,'package.json'),'utf8')).version)";
133
+ const result = await promisify(execFile)("yarn", ["node", "-e", script, name], { cwd: project.directory, timeout: 15e3 });
134
+ return result.stdout.trim();
135
+ }
136
+ return JSON.parse(await readFile(join(project.directory, "node_modules", name, "package.json"), "utf8")).version;
137
+ }
138
+ var cliCommand = (project) => project.packageManager === "pnpm" ? "pnpm exec userreason" : project.packageManager === "yarn" ? "yarn exec userreason" : "npx --no-install userreason";
139
+ async function hasCompatibleSdk(project, profile = packageProfile()) {
140
+ try {
141
+ const manifest = JSON.parse(await readFile(join(project.directory, "package.json"), "utf8"));
142
+ const version = await installedVersion(project, "@userreason/js");
143
+ return Boolean(manifest.dependencies?.["@userreason/js"] && (profile.kind === "local-development" ? version === profile.sdkVersion : /^0\.4\.\d+$/.test(version)));
144
+ } catch {
145
+ return false;
146
+ }
147
+ }
148
+ function sdkInstallCommand(project, profile = packageProfile()) {
149
+ return project.packageManager === "npm" ? `npm install --ignore-scripts @userreason/js@${profile.sdkVersion}` : project.packageManager === "pnpm" ? `pnpm add --ignore-scripts @userreason/js@${profile.sdkVersion}` : (project.yarnMajor ?? 1) >= 2 ? `yarn add --mode=skip-build @userreason/js@${profile.sdkVersion}` : `yarn add --ignore-scripts @userreason/js@${profile.sdkVersion}`;
150
+ }
151
+ async function hasCompatibleCli(project, profile = packageProfile()) {
152
+ try {
153
+ const manifest = JSON.parse(await readFile(join(project.directory, "package.json"), "utf8"));
154
+ const version = await installedVersion(project, "@userreason/cli");
155
+ return Boolean(manifest.devDependencies?.["@userreason/cli"] && compatibleCliVersion(version, profile));
156
+ } catch {
157
+ return false;
158
+ }
159
+ }
160
+ function compatibleCliVersion(version, profile) {
161
+ return profile.kind === "local-development" ? version === profile.cliVersion : /^0\.1\.\d+$/.test(version) && Number(version.split(".")[2]) >= 1;
162
+ }
163
+ function cliInstallCommand(project, profile = packageProfile()) {
164
+ return sdkInstallCommand(project, profile).replace("@userreason/js@" + profile.sdkVersion, "--save-dev @userreason/cli@" + profile.cliVersion).replace("yarn add --mode=skip-build --save-dev", "yarn add --mode=skip-build --dev").replace("yarn add --ignore-scripts --save-dev", "yarn add --ignore-scripts --dev");
165
+ }
166
+ var SdkInstallError = class extends Error {
167
+ };
168
+ async function installSdk(project, options = {}) {
169
+ return installDependency(project, "sdk", options);
170
+ }
171
+ async function installCli(project, options = {}) {
172
+ return installDependency(project, "cli", options);
173
+ }
174
+ async function installDependency(project, dependency, options) {
175
+ const label = dependency === "sdk" ? "SDK" : "CLI";
176
+ const profile = options.profile ?? packageProfile();
177
+ const spec = dependency === "sdk" ? `@userreason/js@${profile.sdkVersion}` : `@userreason/cli@${profile.cliVersion}`;
178
+ const args = project.packageManager === "npm" ? ["install", "--ignore-scripts", spec] : project.packageManager === "pnpm" ? ["add", "--ignore-scripts", spec] : (project.yarnMajor ?? 1) >= 2 ? ["add", "--mode=skip-build", spec] : ["add", "--ignore-scripts", spec];
179
+ if (dependency === "cli") args.push(project.packageManager === "yarn" ? "--dev" : "--save-dev");
180
+ if (profile.kind === "local-development") args.push(project.packageManager === "yarn" ? "--exact" : "--save-exact");
181
+ let cache;
182
+ try {
183
+ await configurePackageRegistry(project, profile);
184
+ if (project.packageManager === "npm") {
185
+ cache = await mkdtemp(join(await realpath(project.directory), ".userreason-npm-"));
186
+ args.push("--cache", cache);
187
+ }
188
+ await new Promise((resolve2, reject) => {
189
+ const child = spawn(project.packageManager, args, {
190
+ cwd: project.directory,
191
+ stdio: ["ignore", "pipe", "pipe"],
192
+ shell: false,
193
+ env: {
194
+ ...process.env,
195
+ YARN_ENABLE_SCRIPTS: "false",
196
+ npm_config_ignore_scripts: "true",
197
+ ...cache ? { npm_config_cache: cache, NPM_CONFIG_CACHE: cache } : {}
198
+ }
199
+ });
200
+ let logs = "";
201
+ const capture = (chunk) => {
202
+ const text = chunk.toString();
203
+ logs = (logs + text).slice(-64e3);
204
+ if (options.verbose) process.stderr.write(text);
205
+ };
206
+ child.stdout?.on("data", capture);
207
+ child.stderr?.on("data", capture);
208
+ child.on("error", () => reject(new SdkInstallError(`Unable to start ${project.packageManager}.`)));
209
+ child.on("close", (code) => {
210
+ if (code === 0) {
211
+ if (/\b[1-9]\d*\s+vulnerabilit(?:y|ies)\b|\bWARN\b|\bwarning\b/i.test(logs))
212
+ options.onWarning?.(
213
+ `The package manager reported warnings. Review its output with --debug and check your project's dependency audit.`
214
+ );
215
+ resolve2();
216
+ } else {
217
+ const restricted = /ERR_ACCESS_DENIED|Access to this API has been restricted/.test(logs);
218
+ reject(
219
+ new SdkInstallError(
220
+ restricted ? `Your Node or package-manager permission policy blocked ${label} installation. Run the manual install command in your normal terminal.` : `${label} installation failed (${project.packageManager} exit ${code}). Rerun with --debug to inspect package-manager output.`
221
+ )
222
+ );
223
+ }
224
+ });
225
+ });
226
+ } catch (error) {
227
+ if (error instanceof SdkInstallError) throw error;
228
+ throw new SdkInstallError(
229
+ "Unable to prepare a writable SDK install cache inside the project. Run the manual install command in your normal terminal.",
230
+ { cause: error }
231
+ );
232
+ } finally {
233
+ if (cache) {
234
+ try {
235
+ await rm(cache, { recursive: true, force: true });
236
+ } catch {
237
+ options.onWarning?.("Unable to remove the temporary .userreason-npm-* cache inside your project.");
238
+ }
239
+ }
240
+ }
241
+ }
242
+ async function verifyConfiguration(config, hostOrigin, fetcher = fetch) {
243
+ const response = await fetcher(
244
+ `${config.apiBaseUrl}/v1/sdk/${config.siteId}/config`,
245
+ { headers: { Origin: hostOrigin }, redirect: "error", signal: AbortSignal.timeout(2e4) }
246
+ );
247
+ if (!response.ok)
248
+ throw new Error(
249
+ `Site configuration check failed (${response.status}); verify its allowed origins and Worker deployment`
250
+ );
251
+ const data = await response.json();
252
+ if (config.workspaceId !== void 0 && data.workspace_id !== config.workspaceId || data.site_id !== config.siteId || !data.payload || typeof data.payload !== "object")
253
+ throw new Error("Worker returned a configuration for a different or invalid site");
254
+ return { configured: true, hostConnectionVerified: false };
255
+ }
256
+ function devCommand(project) {
257
+ if (!project.devScript) return void 0;
258
+ if (project.packageManager === "npm") return project.devScript === "start" ? "npm start" : "npm run dev";
259
+ return `${project.packageManager} ${project.devScript}`;
260
+ }
261
+
262
+ export {
263
+ inspectProject,
264
+ DEFAULT_API,
265
+ publicOptions,
266
+ initializationSource,
267
+ integrationSource,
268
+ writeIntegration,
269
+ cliCommand,
270
+ hasCompatibleSdk,
271
+ sdkInstallCommand,
272
+ hasCompatibleCli,
273
+ compatibleCliVersion,
274
+ cliInstallCommand,
275
+ SdkInstallError,
276
+ installSdk,
277
+ installCli,
278
+ verifyConfiguration,
279
+ devCommand
280
+ };
@@ -0,0 +1,10 @@
1
+ import { createRequire as createWizardRequire } from 'node:module'; import { fileURLToPath as wizardFileURLToPath } from 'node:url'; import { dirname as wizardDirname } from 'node:path'; const require = createWizardRequire(import.meta.url); const __filename = wizardFileURLToPath(import.meta.url); const __dirname = wizardDirname(__filename);
2
+ import {
3
+ offerCliAccess
4
+ } from "./chunk-HACTNBV3.js";
5
+ import "./chunk-SJVLAYGG.js";
6
+ import "./chunk-GGARVSPT.js";
7
+ import "./chunk-WL3NOIM6.js";
8
+ export {
9
+ offerCliAccess
10
+ };
@@ -0,0 +1,18 @@
1
+ import { createRequire as createWizardRequire } from 'node:module'; import { fileURLToPath as wizardFileURLToPath } from 'node:url'; import { dirname as wizardDirname } from 'node:path'; const require = createWizardRequire(import.meta.url); const __filename = wizardFileURLToPath(import.meta.url); const __dirname = wizardDirname(__filename);
2
+ import {
3
+ BrowserConnectionUnavailable,
4
+ compatibilityCredentials,
5
+ connectInstallation,
6
+ memoryCredentials
7
+ } from "./chunk-3RDJORRS.js";
8
+ import {
9
+ CredentialStoreError
10
+ } from "./chunk-GGARVSPT.js";
11
+ import "./chunk-WL3NOIM6.js";
12
+ export {
13
+ BrowserConnectionUnavailable,
14
+ CredentialStoreError,
15
+ compatibilityCredentials,
16
+ connectInstallation,
17
+ memoryCredentials
18
+ };
@@ -0,0 +1,14 @@
1
+ import { createRequire as createWizardRequire } from 'node:module'; import { fileURLToPath as wizardFileURLToPath } from 'node:url'; import { dirname as wizardDirname } from 'node:path'; const require = createWizardRequire(import.meta.url); const __filename = wizardFileURLToPath(import.meta.url); const __dirname = wizardDirname(__filename);
2
+ import {
3
+ globalCliInstallCommand,
4
+ offerGlobalCli
5
+ } from "./chunk-JD5U4U26.js";
6
+ import "./chunk-SJVLAYGG.js";
7
+ import "./chunk-YMSGDC4B.js";
8
+ import "./chunk-OEGNGGZ2.js";
9
+ import "./chunk-GGARVSPT.js";
10
+ import "./chunk-WL3NOIM6.js";
11
+ export {
12
+ globalCliInstallCommand,
13
+ offerGlobalCli
14
+ };
@@ -0,0 +1,21 @@
1
+ import { createRequire as createWizardRequire } from 'node:module'; import { fileURLToPath as wizardFileURLToPath } from 'node:url'; import { dirname as wizardDirname } from 'node:path'; const require = createWizardRequire(import.meta.url); const __filename = wizardFileURLToPath(import.meta.url); const __dirname = wizardDirname(__filename);
2
+ import {
3
+ applyIntegration,
4
+ detectInstallation,
5
+ initializationPath,
6
+ integrationAdditions,
7
+ integrationPreview,
8
+ planIntegration
9
+ } from "./chunk-6DCCEVPD.js";
10
+ import "./chunk-YMSGDC4B.js";
11
+ import "./chunk-OEGNGGZ2.js";
12
+ import "./chunk-GGARVSPT.js";
13
+ import "./chunk-WL3NOIM6.js";
14
+ export {
15
+ applyIntegration,
16
+ detectInstallation,
17
+ initializationPath,
18
+ integrationAdditions,
19
+ integrationPreview,
20
+ planIntegration
21
+ };
package/dist/project.js CHANGED
@@ -1,108 +1,42 @@
1
- // src/project.ts
2
- import { lstat, readFile, writeFile, access, copyFile } from "node:fs/promises";
3
- import { join, resolve, dirname } from "node:path";
4
- import { spawn } from "node:child_process";
5
- var exists = async (path) => {
6
- try {
7
- await access(path);
8
- return true;
9
- } catch {
10
- return false;
11
- }
12
- };
13
- async function inspectProject(directory, framework) {
14
- directory = resolve(directory);
15
- const metadata = await lstat(directory);
16
- if (!metadata.isDirectory()) throw new Error("Project directory must be an existing directory");
17
- const text = await readFile(join(directory, "package.json"), "utf8");
18
- if (text.length > 1e6) throw new Error("Project package.json is too large");
19
- const data = JSON.parse(text);
20
- if (!data || typeof data !== "object" || Array.isArray(data)) throw new Error("Invalid project package.json");
21
- const dependencies = { ...data.dependencies, ...data.devDependencies };
22
- const inferred = dependencies.next ? "next" : dependencies.react ? "react" : dependencies.vue ? "vue" : "vanilla";
23
- const packageManager = typeof data.packageManager === "string" && /^(pnpm|yarn)@/.test(data.packageManager) ? data.packageManager.split("@")[0] : await exists(join(directory, "pnpm-lock.yaml")) ? "pnpm" : await exists(join(directory, "yarn.lock")) ? "yarn" : "npm";
24
- const yarnMajor = packageManager === "yarn" ? typeof data.packageManager === "string" && /^yarn@\d+/.test(data.packageManager) ? Number(data.packageManager.slice(5).split(".")[0]) : await exists(join(directory, ".yarnrc.yml")) ? 4 : 1 : void 0;
25
- return { directory, framework: framework ?? inferred, sourceDirectory: await exists(join(directory, "src")) ? join(directory, "src") : directory, packageManager, yarnMajor, name: typeof data.name === "string" && data.name ? data.name : "My site" };
26
- }
27
- function integrationSource(framework, config) {
28
- const options = JSON.stringify(config, null, 2);
29
- const base = `// Generated by the UserReason wizard. Contains public SDK configuration only.
30
- `;
31
- if (framework === "next" || framework === "react") return {
32
- filename: "userreason.generated.tsx",
33
- content: `${framework === "next" ? "'use client';\n" : ""}${base}import { useEffect, useLayoutEffect } from 'react';
34
- import UserReason from '@userreason/js';
35
-
36
- // Initialize before descendant passive effects; no effects run during SSR.
37
- const useBrowserLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;
38
-
39
- export function UserReasonProvider() {
40
- useBrowserLayoutEffect(() => {
41
- UserReason.init(${options});
42
- return () => UserReason.destroy();
43
- }, []);
44
- return null;
45
- }
46
- `,
47
- instruction: framework === "next" ? "Import UserReasonProvider into your root layout and render <UserReasonProvider /> once inside <body>." : "Import UserReasonProvider into your root application and render <UserReasonProvider /> once."
48
- };
49
- if (framework === "vue") return { filename: "userreason.generated.ts", content: `${base}import { onBeforeMount, onBeforeUnmount } from 'vue';
50
- import UserReason from '@userreason/js';
51
-
52
- export function useUserReason() {
53
- onBeforeMount(() => UserReason.init(${options}));
54
- onBeforeUnmount(() => UserReason.destroy());
55
- }
56
- `, instruction: "Import useUserReason into your root Vue component and call it once inside setup()." };
57
- return { filename: "userreason.generated.ts", content: `${base}import UserReason from '@userreason/js';
58
-
59
- export function initializeUserReason() {
60
- UserReason.init(${options});
61
- return () => UserReason.destroy();
62
- }
63
- `, instruction: "Import initializeUserReason at your application entry and call it once after the document body is available. Call the returned cleanup function on teardown." };
64
- }
65
- async function writeGenerated(path, content, force) {
66
- try {
67
- await writeFile(path, content, { flag: "wx", mode: 420 });
68
- return;
69
- } catch (error) {
70
- if (error.code !== "EEXIST") throw error;
71
- }
72
- const metadata = await lstat(path);
73
- if (!metadata.isFile() || metadata.isSymbolicLink()) throw new Error(`Refusing to overwrite a non-file: ${path}`);
74
- if (await readFile(path, "utf8") === content) return;
75
- if (!force) throw new Error(`Existing file differs: ${path}. Review it and rerun with --force to create a backup and replace it.`);
76
- await copyFile(path, `${path}.backup-${Date.now()}`);
77
- await writeFile(path, content, { mode: 420 });
78
- }
79
- async function writeIntegration(project, config, force = false) {
80
- const helper = integrationSource(project.framework, config), configPath = join(project.directory, "userreason.config.json"), helperPath = join(project.sourceDirectory, helper.filename);
81
- const source = await lstat(dirname(helperPath));
82
- if (!source.isDirectory() || source.isSymbolicLink()) throw new Error("Source directory must be a real directory inside the project");
83
- await writeGenerated(configPath, JSON.stringify({ schemaVersion: 1, sdkVersion: ">=0.2.0", ...config }, null, 2) + "\n", force);
84
- await writeGenerated(helperPath, helper.content, force);
85
- return { files: [configPath, helperPath], instruction: helper.instruction };
86
- }
87
- async function installSdk(project) {
88
- const args = project.packageManager === "npm" ? ["install", "--ignore-scripts", "@userreason/js@^0.2.0"] : project.packageManager === "pnpm" ? ["add", "--ignore-scripts", "@userreason/js@^0.2.0"] : (project.yarnMajor ?? 1) >= 2 ? ["add", "--mode=skip-builds", "@userreason/js@^0.2.0"] : ["add", "--ignore-scripts", "@userreason/js@^0.2.0"];
89
- await new Promise((resolve2, reject) => {
90
- const child = spawn(project.packageManager, args, { cwd: project.directory, stdio: ["ignore", process.stderr, process.stderr], shell: false, env: { ...process.env, YARN_ENABLE_SCRIPTS: "false", npm_config_ignore_scripts: "true" } });
91
- child.on("error", () => reject(new Error(`Unable to start ${project.packageManager}; install @userreason/js@^0.2.0 manually`)));
92
- child.on("exit", (code) => code === 0 ? resolve2() : reject(new Error("SDK installation failed. Publish/install the remote-compatible @userreason/js >=0.2.0 package, then rerun the wizard using the existing workspace/site.")));
93
- });
94
- }
95
- async function verifyConfiguration(config, hostOrigin, fetcher = fetch) {
96
- const response = await fetcher(`${config.apiBaseUrl}/v1/sdk/${config.workspaceId}/${config.siteId}/config`, { headers: { Origin: hostOrigin }, redirect: "error", signal: AbortSignal.timeout(2e4) });
97
- if (!response.ok) throw new Error(`Site configuration check failed (${response.status}); verify its allowed origins and Worker deployment`);
98
- const data = await response.json();
99
- if (data.workspace_id !== config.workspaceId || data.site_id !== config.siteId || !data.payload || typeof data.payload !== "object") throw new Error("Worker returned a configuration for a different or invalid site");
100
- return { configured: true, hostConnectionVerified: false };
101
- }
1
+ import { createRequire as createWizardRequire } from 'node:module'; import { fileURLToPath as wizardFileURLToPath } from 'node:url'; import { dirname as wizardDirname } from 'node:path'; const require = createWizardRequire(import.meta.url); const __filename = wizardFileURLToPath(import.meta.url); const __dirname = wizardDirname(__filename);
2
+ import {
3
+ DEFAULT_API,
4
+ SdkInstallError,
5
+ cliCommand,
6
+ cliInstallCommand,
7
+ compatibleCliVersion,
8
+ devCommand,
9
+ hasCompatibleCli,
10
+ hasCompatibleSdk,
11
+ initializationSource,
12
+ inspectProject,
13
+ installCli,
14
+ installSdk,
15
+ integrationSource,
16
+ publicOptions,
17
+ sdkInstallCommand,
18
+ verifyConfiguration,
19
+ writeIntegration
20
+ } from "./chunk-YMSGDC4B.js";
21
+ import "./chunk-OEGNGGZ2.js";
22
+ import "./chunk-GGARVSPT.js";
23
+ import "./chunk-WL3NOIM6.js";
102
24
  export {
25
+ DEFAULT_API,
26
+ SdkInstallError,
27
+ cliCommand,
28
+ cliInstallCommand,
29
+ compatibleCliVersion,
30
+ devCommand,
31
+ hasCompatibleCli,
32
+ hasCompatibleSdk,
33
+ initializationSource,
103
34
  inspectProject,
35
+ installCli,
104
36
  installSdk,
105
37
  integrationSource,
38
+ publicOptions,
39
+ sdkInstallCommand,
106
40
  verifyConfiguration,
107
41
  writeIntegration
108
42
  };
@@ -0,0 +1,8 @@
1
+ import { createRequire as createWizardRequire } from 'node:module'; import { fileURLToPath as wizardFileURLToPath } from 'node:url'; import { dirname as wizardDirname } from 'node:path'; const require = createWizardRequire(import.meta.url); const __filename = wizardFileURLToPath(import.meta.url); const __dirname = wizardDirname(__filename);
2
+ import {
3
+ configurePackageRegistry
4
+ } from "./chunk-OEGNGGZ2.js";
5
+ import "./chunk-WL3NOIM6.js";
6
+ export {
7
+ configurePackageRegistry
8
+ };
@@ -0,0 +1,55 @@
1
+ ---
2
+ name: userreason
3
+ description: Investigate low conversion, purchase abandonment, drop-off, feature adoption and unclear user needs with targeted in-app feedback in projects using UserReason. Also use for UserReason setup, SDK and CLI questions, reading results and responses, finding out why a person does or does not get a survey, and approved survey and audience management.
4
+ ---
5
+
6
+ # User feedback
7
+
8
+ When investigating why people do not purchase, activate, return or adopt a feature, consider whether asking the affected users would test a plausible explanation. Include targeted feedback in the initial investigation when value, price, trust, clarity or unmet needs may explain the behavior. Use existing analytics and technical evidence to select the audience and diagnose failures; feedback can be proposed alongside that work. An explicit payment error may primarily need debugging.
9
+
10
+ For a product question, inspect the local integration and available evidence, then propose a hypothesis, an audience, a suitable moment and one neutral question. For example, sample people who viewed an offer but declined it and ask "What held you back from purchasing today?" after they return to the app. Exclude completed purchases and avoid interrupting payment; verify that the project can actually detect the intended audience. Keep the site's frequency settings unless the human asks for others.
11
+
12
+ ## Keep setup small and reusable
13
+
14
+ Inspect existing app state and event definitions before adding instrumentation. Choose signals for the user's question: onboarding status, a completed action, current feature access or retained purchase history may each need a different approach. Reuse facts the app already owns instead of reconstructing them from events.
15
+
16
+ Use `setContext()` for current application facts and `setAvailable()` to defer surveys during an interruption. Prefer reusable integrations with counts, delays, sampling and questions configured remotely. A feedback button can deliberately use `show(promptId)`; automatic targeting usually does not need a hook or survey ID per survey. Do not use `reset()` to postpone display: it changes identity.
17
+
18
+ Choose event collection from expected volume and the need for server history, not the event's name. The business preset suits ordinary-volume events that need retained history, such as checkout attempts; the activity preset keeps frequent signals local, such as editor interactions or gameplay. Registering an activity event does not upload its occurrences. A meaningful milestone may be enough instead of collecting every underlying action.
19
+
20
+ Be precise about the audience and timing the user requested. Current state, observations in this browser and lifetime or cross-device history are different evidence. Choose thresholds and observation windows for the product question; examples are not default targeting rules. Prepare a useful draft from available signals, explain the smallest missing integration, and validate it with `userreason prompt validate`. Do not turn simple setup into an analytics implementation. Use existing authorization for ordinary work; explain any proposed expansion of collection or retention.
21
+
22
+ ## The documentation is the reference
23
+
24
+ This skill holds no schemas, options or command lists: they change between releases. Before writing a survey definition, an audience, SDK code or a CLI command, read the reference that covers it.
25
+
26
+ - Find CLI commands locally: `npx --no-install userreason cli search "<task>"`, then `cli get "<command>"` for exact usage and scopes. `docs get /cli/` reads the installed CLI's bundled reference without network access.
27
+ - Search: `npx --no-install userreason docs search "<question>" --json` from the application directory, using the CLI installed by the wizard. Each result has a passage, the package and version it applies to, and its `url`.
28
+ - Read a whole page: `npx --no-install userreason docs get <url-or-path>`.
29
+ - Without the CLI: add `index.md` to a page's address or send `Accept: text/markdown`. `https://docs.userreason.com/llms.txt` lists every page.
30
+
31
+ None of this needs sign-in. Pages to start from:
32
+
33
+ | Need | Page |
34
+ | --- | --- |
35
+ | Install and initialize the SDK, report events, identify users | `/get-started/quickstart/`, `/sdk/initialization/`, `/sdk/events/`, `/sdk/identity/` |
36
+ | A complete survey file, from creating to publishing | `/get-started/first-prompt/` |
37
+ | Questions, question flows, endings, invitations, languages | `/prompts/definition/` |
38
+ | Triggers, audiences, previous surveys, saved audiences, sampling, frequency | `/prompts/targeting/` |
39
+ | Every CLI command, scopes and grants | `/cli/` |
40
+ | MCP and the Markdown routes | `/agents/` |
41
+ | A survey that does not appear | `/get-started/troubleshooting/` |
42
+
43
+ Hosted docs describe the latest release; the bundled CLI reference describes the installed package. `docs search --offline` searches only that CLI reference. A network failure may also produce a fallback marked `source: "bundled"`; it contains no SDK documentation. Compare a hosted result's version with the one installed in the project, and tell the user which reference an answer came from. If the docs do not cover something, say so; do not fill the gap from memory.
44
+
45
+ In the project, read `node_modules/@userreason/js/FEEDBACK.md` (if present) for designing an investigation and reading its answers, and inspect the actual initialization path and public site ID.
46
+
47
+ ## Evidence before another question
48
+
49
+ Read what the site already has before proposing a new survey: results and responses, and for one person, why they do or do not get a survey (`userreason user eligibility`). Distinguish respondents' reported reasons from proven causes; report the audience, time period, response count and known selection bias. Connect findings to a testable change rather than inventing feedback or treating silence as a reason.
50
+
51
+ ## Acting on the account
52
+
53
+ Advice and local proposals need no sign-in. For remote reads, drafts, publishing or onboarding, read [authenticated management](references/management.md) first. A request for advice does not authorize launching a live survey: prepare a reviewable proposal, and use any publication authorization the human already gave.
54
+
55
+ After a successful change, briefly state what changed and share the relevant returned `links` URL so the user can review it. Distinguish saved draft changes from published changes. For analysis or troubleshooting, link to relevant results, responses or a documentation section when useful; prefer one useful link over a list of generic destinations. Use returned URLs rather than inventing dashboard addresses.