@stephenov/feedbackwidget 0.2.2 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -45,13 +45,6 @@ async function main() {
45
45
  }
46
46
  async function initProject() {
47
47
  console.log("\n\u{1F3A4} feedbackwidget - Voice-first feedback for your app\n");
48
- const existingKey = getStoredApiKey();
49
- if (existingKey) {
50
- console.log("\u2705 Already configured!");
51
- console.log(` API Key: ${existingKey.slice(0, 20)}...`);
52
- console.log("\n To reconfigure, delete .feedbackwidgetrc and run again.\n");
53
- return;
54
- }
55
48
  const state = (0, import_crypto.randomBytes)(16).toString("hex");
56
49
  const apiKey = await new Promise((resolve, reject) => {
57
50
  const server = import_http.default.createServer((req, res) => {
@@ -95,48 +88,44 @@ async function initProject() {
95
88
  reject(new Error("Authentication timed out"));
96
89
  }, 5 * 60 * 1e3);
97
90
  });
98
- saveApiKey(apiKey);
99
- const snippet = `import { FeedbackWidget } from "@stephenov/feedbackwidget"
100
-
101
- <FeedbackWidget apiKey="${apiKey}" />`;
91
+ const snippet = `<FeedbackWidget apiKey="${apiKey}" />`;
92
+ let copied = false;
102
93
  try {
103
94
  await copyToClipboard(snippet);
104
- console.log("\u2705 Authenticated successfully!\n");
105
- console.log(` API Key: ${apiKey.slice(0, 20)}...`);
106
- console.log(" Saved to .feedbackwidgetrc\n");
107
- console.log(" \u{1F4CB} Code snippet copied to clipboard! Just paste into your app.\n");
95
+ copied = true;
108
96
  } catch {
109
- console.log("\u2705 Authenticated successfully!\n");
110
- console.log(` API Key: ${apiKey.slice(0, 20)}...`);
111
- console.log(" Saved to .feedbackwidgetrc\n");
112
- console.log(" Add to your app:\n");
113
- console.log(' import { FeedbackWidget } from "@stephenov/feedbackwidget"');
114
- console.log(` <FeedbackWidget apiKey="${apiKey}" />
115
- `);
116
97
  }
117
- }
118
- async function whoami() {
119
- const apiKey = getStoredApiKey();
120
- if (!apiKey) {
121
- console.log("\nNot logged in. Run: npx @stephenov/feedbackwidget init\n");
122
- return;
98
+ console.log("\u2705 Authenticated!\n");
99
+ console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
100
+ console.log(" Your API Key:\n");
101
+ console.log(` ${apiKey}
102
+ `);
103
+ console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
104
+ if (copied) {
105
+ console.log(" \u{1F4CB} Copied to clipboard!\n");
123
106
  }
124
- try {
125
- const res = await fetch(`${API_BASE}/api/v1/validate`, {
126
- headers: { Authorization: `Bearer ${apiKey}` }
127
- });
128
- const data = await res.json();
129
- if (data.valid) {
130
- console.log(`
131
- Logged in as: ${data.project?.name || "Unknown"}`);
132
- console.log(`Plan: ${data.limits?.submissions || 100} submissions/month
107
+ const framework = detectFramework();
108
+ console.log(" Add to your app:\n");
109
+ console.log(' import { FeedbackWidget } from "@stephenov/feedbackwidget"\n');
110
+ console.log(` ${snippet}
111
+ `);
112
+ if (framework.name) {
113
+ console.log(` Detected: ${framework.name}
114
+ `);
115
+ console.log(` Add to: ${framework.file}
116
+ `);
117
+ if (framework.note) {
118
+ console.log(` Note: ${framework.note}
133
119
  `);
134
- } else {
135
- console.log("\nInvalid API key. Run: npx @stephenov/feedbackwidget init\n");
136
120
  }
137
- } catch {
138
- console.log("\nCouldn't verify API key. Check your connection.\n");
121
+ } else {
122
+ console.log(" Add to your root component (renders on every page)\n");
139
123
  }
124
+ console.log(" Dashboard: https://feedbackwidget-api.vercel.app/dashboard\n");
125
+ }
126
+ async function whoami() {
127
+ console.log("\nRun 'npx feedbackwidget init' to get your API key.\n");
128
+ console.log("Then check your dashboard at: https://feedbackwidget-api.vercel.app/dashboard\n");
140
129
  }
141
130
  function showHelp() {
142
131
  console.log(`
@@ -151,29 +140,6 @@ Usage:
151
140
  npx @stephenov/feedbackwidget whoami
152
141
  `);
153
142
  }
154
- function getStoredApiKey() {
155
- const rcPath = import_path.default.join(process.cwd(), ".feedbackwidgetrc");
156
- try {
157
- const content = import_fs.default.readFileSync(rcPath, "utf-8");
158
- const match = content.match(/FEEDBACKWIDGET_API_KEY=(.+)/);
159
- return match ? match[1].trim() : null;
160
- } catch {
161
- return null;
162
- }
163
- }
164
- function saveApiKey(apiKey) {
165
- const rcPath = import_path.default.join(process.cwd(), ".feedbackwidgetrc");
166
- import_fs.default.writeFileSync(rcPath, `FEEDBACKWIDGET_API_KEY=${apiKey}
167
- `);
168
- const gitignorePath = import_path.default.join(process.cwd(), ".gitignore");
169
- try {
170
- const gitignore = import_fs.default.readFileSync(gitignorePath, "utf-8");
171
- if (!gitignore.includes(".feedbackwidgetrc")) {
172
- import_fs.default.appendFileSync(gitignorePath, "\n.feedbackwidgetrc\n");
173
- }
174
- } catch {
175
- }
176
- }
177
143
  function successPage() {
178
144
  return `
179
145
  <!DOCTYPE html>
@@ -220,6 +186,62 @@ function errorPage(error) {
220
186
  </body>
221
187
  </html>`;
222
188
  }
189
+ function detectFramework() {
190
+ const cwd = process.cwd();
191
+ try {
192
+ const pkgPath = import_path.default.join(cwd, "package.json");
193
+ const pkg = JSON.parse(import_fs.default.readFileSync(pkgPath, "utf-8"));
194
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
195
+ if (deps.next && import_fs.default.existsSync(import_path.default.join(cwd, "app"))) {
196
+ if (import_fs.default.existsSync(import_path.default.join(cwd, "app", "layout.tsx"))) {
197
+ return { name: "Next.js (App Router)", file: "app/layout.tsx", note: "Add inside <body> before {children}" };
198
+ }
199
+ if (import_fs.default.existsSync(import_path.default.join(cwd, "app", "layout.js"))) {
200
+ return { name: "Next.js (App Router)", file: "app/layout.js", note: "Add inside <body> before {children}" };
201
+ }
202
+ if (import_fs.default.existsSync(import_path.default.join(cwd, "src", "app", "layout.tsx"))) {
203
+ return { name: "Next.js (App Router)", file: "src/app/layout.tsx", note: "Add inside <body> before {children}" };
204
+ }
205
+ }
206
+ if (deps.next && import_fs.default.existsSync(import_path.default.join(cwd, "pages"))) {
207
+ if (import_fs.default.existsSync(import_path.default.join(cwd, "pages", "_app.tsx"))) {
208
+ return { name: "Next.js (Pages Router)", file: "pages/_app.tsx", note: "Add after <Component {...pageProps} />" };
209
+ }
210
+ if (import_fs.default.existsSync(import_path.default.join(cwd, "pages", "_app.js"))) {
211
+ return { name: "Next.js (Pages Router)", file: "pages/_app.js", note: "Add after <Component {...pageProps} />" };
212
+ }
213
+ }
214
+ if (deps.vite && (deps.react || deps["@vitejs/plugin-react"])) {
215
+ if (import_fs.default.existsSync(import_path.default.join(cwd, "src", "App.tsx"))) {
216
+ return { name: "Vite + React", file: "src/App.tsx", note: "Add at the end of your App component" };
217
+ }
218
+ if (import_fs.default.existsSync(import_path.default.join(cwd, "src", "App.jsx"))) {
219
+ return { name: "Vite + React", file: "src/App.jsx", note: "Add at the end of your App component" };
220
+ }
221
+ }
222
+ if (deps["react-scripts"]) {
223
+ if (import_fs.default.existsSync(import_path.default.join(cwd, "src", "App.tsx"))) {
224
+ return { name: "Create React App", file: "src/App.tsx", note: "Add at the end of your App component" };
225
+ }
226
+ if (import_fs.default.existsSync(import_path.default.join(cwd, "src", "App.js"))) {
227
+ return { name: "Create React App", file: "src/App.js", note: "Add at the end of your App component" };
228
+ }
229
+ }
230
+ if (deps.astro) {
231
+ return { name: "Astro", file: "Create a React component", note: "Use client:load directive" };
232
+ }
233
+ if (deps["@remix-run/react"]) {
234
+ if (import_fs.default.existsSync(import_path.default.join(cwd, "app", "root.tsx"))) {
235
+ return { name: "Remix", file: "app/root.tsx", note: "Add inside <body>" };
236
+ }
237
+ }
238
+ if (deps.react) {
239
+ return { name: "React", file: "Your main App component", note: "Add anywhere it renders on every page" };
240
+ }
241
+ } catch {
242
+ }
243
+ return { name: null, file: "Your root component" };
244
+ }
223
245
  async function copyToClipboard(text) {
224
246
  return new Promise((resolve, reject) => {
225
247
  const platform = process.platform;
package/dist/cli.mjs CHANGED
@@ -22,13 +22,6 @@ async function main() {
22
22
  }
23
23
  async function initProject() {
24
24
  console.log("\n\u{1F3A4} feedbackwidget - Voice-first feedback for your app\n");
25
- const existingKey = getStoredApiKey();
26
- if (existingKey) {
27
- console.log("\u2705 Already configured!");
28
- console.log(` API Key: ${existingKey.slice(0, 20)}...`);
29
- console.log("\n To reconfigure, delete .feedbackwidgetrc and run again.\n");
30
- return;
31
- }
32
25
  const state = randomBytes(16).toString("hex");
33
26
  const apiKey = await new Promise((resolve, reject) => {
34
27
  const server = http.createServer((req, res) => {
@@ -72,48 +65,44 @@ async function initProject() {
72
65
  reject(new Error("Authentication timed out"));
73
66
  }, 5 * 60 * 1e3);
74
67
  });
75
- saveApiKey(apiKey);
76
- const snippet = `import { FeedbackWidget } from "@stephenov/feedbackwidget"
77
-
78
- <FeedbackWidget apiKey="${apiKey}" />`;
68
+ const snippet = `<FeedbackWidget apiKey="${apiKey}" />`;
69
+ let copied = false;
79
70
  try {
80
71
  await copyToClipboard(snippet);
81
- console.log("\u2705 Authenticated successfully!\n");
82
- console.log(` API Key: ${apiKey.slice(0, 20)}...`);
83
- console.log(" Saved to .feedbackwidgetrc\n");
84
- console.log(" \u{1F4CB} Code snippet copied to clipboard! Just paste into your app.\n");
72
+ copied = true;
85
73
  } catch {
86
- console.log("\u2705 Authenticated successfully!\n");
87
- console.log(` API Key: ${apiKey.slice(0, 20)}...`);
88
- console.log(" Saved to .feedbackwidgetrc\n");
89
- console.log(" Add to your app:\n");
90
- console.log(' import { FeedbackWidget } from "@stephenov/feedbackwidget"');
91
- console.log(` <FeedbackWidget apiKey="${apiKey}" />
92
- `);
93
74
  }
94
- }
95
- async function whoami() {
96
- const apiKey = getStoredApiKey();
97
- if (!apiKey) {
98
- console.log("\nNot logged in. Run: npx @stephenov/feedbackwidget init\n");
99
- return;
75
+ console.log("\u2705 Authenticated!\n");
76
+ console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
77
+ console.log(" Your API Key:\n");
78
+ console.log(` ${apiKey}
79
+ `);
80
+ console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
81
+ if (copied) {
82
+ console.log(" \u{1F4CB} Copied to clipboard!\n");
100
83
  }
101
- try {
102
- const res = await fetch(`${API_BASE}/api/v1/validate`, {
103
- headers: { Authorization: `Bearer ${apiKey}` }
104
- });
105
- const data = await res.json();
106
- if (data.valid) {
107
- console.log(`
108
- Logged in as: ${data.project?.name || "Unknown"}`);
109
- console.log(`Plan: ${data.limits?.submissions || 100} submissions/month
84
+ const framework = detectFramework();
85
+ console.log(" Add to your app:\n");
86
+ console.log(' import { FeedbackWidget } from "@stephenov/feedbackwidget"\n');
87
+ console.log(` ${snippet}
88
+ `);
89
+ if (framework.name) {
90
+ console.log(` Detected: ${framework.name}
91
+ `);
92
+ console.log(` Add to: ${framework.file}
93
+ `);
94
+ if (framework.note) {
95
+ console.log(` Note: ${framework.note}
110
96
  `);
111
- } else {
112
- console.log("\nInvalid API key. Run: npx @stephenov/feedbackwidget init\n");
113
97
  }
114
- } catch {
115
- console.log("\nCouldn't verify API key. Check your connection.\n");
98
+ } else {
99
+ console.log(" Add to your root component (renders on every page)\n");
116
100
  }
101
+ console.log(" Dashboard: https://feedbackwidget-api.vercel.app/dashboard\n");
102
+ }
103
+ async function whoami() {
104
+ console.log("\nRun 'npx feedbackwidget init' to get your API key.\n");
105
+ console.log("Then check your dashboard at: https://feedbackwidget-api.vercel.app/dashboard\n");
117
106
  }
118
107
  function showHelp() {
119
108
  console.log(`
@@ -128,29 +117,6 @@ Usage:
128
117
  npx @stephenov/feedbackwidget whoami
129
118
  `);
130
119
  }
131
- function getStoredApiKey() {
132
- const rcPath = path.join(process.cwd(), ".feedbackwidgetrc");
133
- try {
134
- const content = fs.readFileSync(rcPath, "utf-8");
135
- const match = content.match(/FEEDBACKWIDGET_API_KEY=(.+)/);
136
- return match ? match[1].trim() : null;
137
- } catch {
138
- return null;
139
- }
140
- }
141
- function saveApiKey(apiKey) {
142
- const rcPath = path.join(process.cwd(), ".feedbackwidgetrc");
143
- fs.writeFileSync(rcPath, `FEEDBACKWIDGET_API_KEY=${apiKey}
144
- `);
145
- const gitignorePath = path.join(process.cwd(), ".gitignore");
146
- try {
147
- const gitignore = fs.readFileSync(gitignorePath, "utf-8");
148
- if (!gitignore.includes(".feedbackwidgetrc")) {
149
- fs.appendFileSync(gitignorePath, "\n.feedbackwidgetrc\n");
150
- }
151
- } catch {
152
- }
153
- }
154
120
  function successPage() {
155
121
  return `
156
122
  <!DOCTYPE html>
@@ -197,6 +163,62 @@ function errorPage(error) {
197
163
  </body>
198
164
  </html>`;
199
165
  }
166
+ function detectFramework() {
167
+ const cwd = process.cwd();
168
+ try {
169
+ const pkgPath = path.join(cwd, "package.json");
170
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
171
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
172
+ if (deps.next && fs.existsSync(path.join(cwd, "app"))) {
173
+ if (fs.existsSync(path.join(cwd, "app", "layout.tsx"))) {
174
+ return { name: "Next.js (App Router)", file: "app/layout.tsx", note: "Add inside <body> before {children}" };
175
+ }
176
+ if (fs.existsSync(path.join(cwd, "app", "layout.js"))) {
177
+ return { name: "Next.js (App Router)", file: "app/layout.js", note: "Add inside <body> before {children}" };
178
+ }
179
+ if (fs.existsSync(path.join(cwd, "src", "app", "layout.tsx"))) {
180
+ return { name: "Next.js (App Router)", file: "src/app/layout.tsx", note: "Add inside <body> before {children}" };
181
+ }
182
+ }
183
+ if (deps.next && fs.existsSync(path.join(cwd, "pages"))) {
184
+ if (fs.existsSync(path.join(cwd, "pages", "_app.tsx"))) {
185
+ return { name: "Next.js (Pages Router)", file: "pages/_app.tsx", note: "Add after <Component {...pageProps} />" };
186
+ }
187
+ if (fs.existsSync(path.join(cwd, "pages", "_app.js"))) {
188
+ return { name: "Next.js (Pages Router)", file: "pages/_app.js", note: "Add after <Component {...pageProps} />" };
189
+ }
190
+ }
191
+ if (deps.vite && (deps.react || deps["@vitejs/plugin-react"])) {
192
+ if (fs.existsSync(path.join(cwd, "src", "App.tsx"))) {
193
+ return { name: "Vite + React", file: "src/App.tsx", note: "Add at the end of your App component" };
194
+ }
195
+ if (fs.existsSync(path.join(cwd, "src", "App.jsx"))) {
196
+ return { name: "Vite + React", file: "src/App.jsx", note: "Add at the end of your App component" };
197
+ }
198
+ }
199
+ if (deps["react-scripts"]) {
200
+ if (fs.existsSync(path.join(cwd, "src", "App.tsx"))) {
201
+ return { name: "Create React App", file: "src/App.tsx", note: "Add at the end of your App component" };
202
+ }
203
+ if (fs.existsSync(path.join(cwd, "src", "App.js"))) {
204
+ return { name: "Create React App", file: "src/App.js", note: "Add at the end of your App component" };
205
+ }
206
+ }
207
+ if (deps.astro) {
208
+ return { name: "Astro", file: "Create a React component", note: "Use client:load directive" };
209
+ }
210
+ if (deps["@remix-run/react"]) {
211
+ if (fs.existsSync(path.join(cwd, "app", "root.tsx"))) {
212
+ return { name: "Remix", file: "app/root.tsx", note: "Add inside <body>" };
213
+ }
214
+ }
215
+ if (deps.react) {
216
+ return { name: "React", file: "Your main App component", note: "Add anywhere it renders on every page" };
217
+ }
218
+ } catch {
219
+ }
220
+ return { name: null, file: "Your root component" };
221
+ }
200
222
  async function copyToClipboard(text) {
201
223
  return new Promise((resolve, reject) => {
202
224
  const platform = process.platform;