@stephenov/feedbackwidget 0.3.1 → 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.
Files changed (3) hide show
  1. package/dist/cli.js +114 -85
  2. package/dist/cli.mjs +114 -85
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -27,6 +27,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
27
  var import_http = __toESM(require("http"));
28
28
  var import_open = __toESM(require("open"));
29
29
  var import_crypto = require("crypto");
30
+ var import_child_process = require("child_process");
30
31
  var import_fs = __toESM(require("fs"));
31
32
  var import_path = __toESM(require("path"));
32
33
  var API_BASE = "https://feedbackwidget-api.vercel.app";
@@ -44,13 +45,6 @@ async function main() {
44
45
  }
45
46
  async function initProject() {
46
47
  console.log("\n\u{1F3A4} feedbackwidget - Voice-first feedback for your app\n");
47
- const existingKey = getStoredApiKey();
48
- if (existingKey) {
49
- console.log("\u2705 Already configured!");
50
- console.log(` API Key: ${existingKey.slice(0, 20)}...`);
51
- console.log("\n To reconfigure, delete .feedbackwidgetrc and run again.\n");
52
- return;
53
- }
54
48
  const state = (0, import_crypto.randomBytes)(16).toString("hex");
55
49
  const apiKey = await new Promise((resolve, reject) => {
56
50
  const server = import_http.default.createServer((req, res) => {
@@ -94,49 +88,44 @@ async function initProject() {
94
88
  reject(new Error("Authentication timed out"));
95
89
  }, 5 * 60 * 1e3);
96
90
  });
97
- const saved = saveApiKeyToEnv(apiKey);
98
- console.log("\u2705 Authenticated successfully!\n");
99
- console.log(` API Key: ${apiKey}
91
+ const snippet = `<FeedbackWidget apiKey="${apiKey}" />`;
92
+ let copied = false;
93
+ try {
94
+ await copyToClipboard(snippet);
95
+ copied = true;
96
+ } catch {
97
+ }
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}
100
102
  `);
101
- if (saved) {
102
- console.log(` \u2713 Saved to ${saved}
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");
106
+ }
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}
103
111
  `);
104
- console.log(" Add ONE line to your app's root layout:\n");
105
- console.log(' import "@stephenov/feedbackwidget/auto"\n');
106
- } else {
107
- console.log(" Add to your .env.local (or .env):\n");
108
- console.log(` NEXT_PUBLIC_FEEDBACKWIDGET_API_KEY=${apiKey}
112
+ if (framework.name) {
113
+ console.log(` Detected: ${framework.name}
109
114
  `);
110
- console.log(" Then add to your app's root layout:\n");
111
- console.log(' import "@stephenov/feedbackwidget/auto"\n');
112
- }
113
- console.log(" Or use directly:\n");
114
- console.log(' import { FeedbackWidget } from "@stephenov/feedbackwidget"');
115
- console.log(` <FeedbackWidget apiKey="${apiKey}" />
115
+ console.log(` Add to: ${framework.file}
116
116
  `);
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;
123
- }
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
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,48 +140,6 @@ Usage:
151
140
  npx @stephenov/feedbackwidget whoami
152
141
  `);
153
142
  }
154
- function getStoredApiKey() {
155
- const envPath = import_path.default.join(process.cwd(), ".env.local");
156
- try {
157
- const content = import_fs.default.readFileSync(envPath, "utf-8");
158
- const match = content.match(/NEXT_PUBLIC_FEEDBACKWIDGET_API_KEY=(.+)/);
159
- return match ? match[1].trim() : null;
160
- } catch {
161
- return null;
162
- }
163
- }
164
- function saveApiKeyToEnv(apiKey) {
165
- const envVar = `NEXT_PUBLIC_FEEDBACKWIDGET_API_KEY=${apiKey}`;
166
- const envFiles = [".env.local", ".env"];
167
- for (const envFile of envFiles) {
168
- const envPath = import_path.default.join(process.cwd(), envFile);
169
- try {
170
- const content = import_fs.default.readFileSync(envPath, "utf-8");
171
- if (content.includes("NEXT_PUBLIC_FEEDBACKWIDGET_API_KEY=")) {
172
- const updated = content.replace(
173
- /NEXT_PUBLIC_FEEDBACKWIDGET_API_KEY=.*/,
174
- envVar
175
- );
176
- import_fs.default.writeFileSync(envPath, updated);
177
- } else {
178
- import_fs.default.appendFileSync(envPath, `
179
- ${envVar}
180
- `);
181
- }
182
- return envFile;
183
- } catch {
184
- continue;
185
- }
186
- }
187
- try {
188
- const envPath = import_path.default.join(process.cwd(), ".env.local");
189
- import_fs.default.writeFileSync(envPath, `${envVar}
190
- `);
191
- return ".env.local";
192
- } catch {
193
- return null;
194
- }
195
- }
196
143
  function successPage() {
197
144
  return `
198
145
  <!DOCTYPE html>
@@ -239,4 +186,86 @@ function errorPage(error) {
239
186
  </body>
240
187
  </html>`;
241
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
+ }
245
+ async function copyToClipboard(text) {
246
+ return new Promise((resolve, reject) => {
247
+ const platform = process.platform;
248
+ let cmd;
249
+ let args = [];
250
+ if (platform === "darwin") {
251
+ cmd = "pbcopy";
252
+ } else if (platform === "linux") {
253
+ cmd = "xclip";
254
+ args = ["-selection", "clipboard"];
255
+ } else if (platform === "win32") {
256
+ cmd = "clip";
257
+ } else {
258
+ reject(new Error("Unsupported platform"));
259
+ return;
260
+ }
261
+ const proc = (0, import_child_process.spawn)(cmd, args);
262
+ proc.stdin.write(text);
263
+ proc.stdin.end();
264
+ proc.on("close", (code) => {
265
+ if (code === 0) resolve();
266
+ else reject(new Error(`Clipboard failed with code ${code}`));
267
+ });
268
+ proc.on("error", reject);
269
+ });
270
+ }
242
271
  main().catch(console.error);
package/dist/cli.mjs CHANGED
@@ -4,6 +4,7 @@
4
4
  import http from "http";
5
5
  import open from "open";
6
6
  import { randomBytes } from "crypto";
7
+ import { spawn } from "child_process";
7
8
  import fs from "fs";
8
9
  import path from "path";
9
10
  var API_BASE = "https://feedbackwidget-api.vercel.app";
@@ -21,13 +22,6 @@ async function main() {
21
22
  }
22
23
  async function initProject() {
23
24
  console.log("\n\u{1F3A4} feedbackwidget - Voice-first feedback for your app\n");
24
- const existingKey = getStoredApiKey();
25
- if (existingKey) {
26
- console.log("\u2705 Already configured!");
27
- console.log(` API Key: ${existingKey.slice(0, 20)}...`);
28
- console.log("\n To reconfigure, delete .feedbackwidgetrc and run again.\n");
29
- return;
30
- }
31
25
  const state = randomBytes(16).toString("hex");
32
26
  const apiKey = await new Promise((resolve, reject) => {
33
27
  const server = http.createServer((req, res) => {
@@ -71,49 +65,44 @@ async function initProject() {
71
65
  reject(new Error("Authentication timed out"));
72
66
  }, 5 * 60 * 1e3);
73
67
  });
74
- const saved = saveApiKeyToEnv(apiKey);
75
- console.log("\u2705 Authenticated successfully!\n");
76
- console.log(` API Key: ${apiKey}
68
+ const snippet = `<FeedbackWidget apiKey="${apiKey}" />`;
69
+ let copied = false;
70
+ try {
71
+ await copyToClipboard(snippet);
72
+ copied = true;
73
+ } catch {
74
+ }
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}
77
79
  `);
78
- if (saved) {
79
- console.log(` \u2713 Saved to ${saved}
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");
83
+ }
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}
80
88
  `);
81
- console.log(" Add ONE line to your app's root layout:\n");
82
- console.log(' import "@stephenov/feedbackwidget/auto"\n');
83
- } else {
84
- console.log(" Add to your .env.local (or .env):\n");
85
- console.log(` NEXT_PUBLIC_FEEDBACKWIDGET_API_KEY=${apiKey}
89
+ if (framework.name) {
90
+ console.log(` Detected: ${framework.name}
86
91
  `);
87
- console.log(" Then add to your app's root layout:\n");
88
- console.log(' import "@stephenov/feedbackwidget/auto"\n');
89
- }
90
- console.log(" Or use directly:\n");
91
- console.log(' import { FeedbackWidget } from "@stephenov/feedbackwidget"');
92
- console.log(` <FeedbackWidget apiKey="${apiKey}" />
92
+ console.log(` Add to: ${framework.file}
93
93
  `);
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;
100
- }
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
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,48 +117,6 @@ Usage:
128
117
  npx @stephenov/feedbackwidget whoami
129
118
  `);
130
119
  }
131
- function getStoredApiKey() {
132
- const envPath = path.join(process.cwd(), ".env.local");
133
- try {
134
- const content = fs.readFileSync(envPath, "utf-8");
135
- const match = content.match(/NEXT_PUBLIC_FEEDBACKWIDGET_API_KEY=(.+)/);
136
- return match ? match[1].trim() : null;
137
- } catch {
138
- return null;
139
- }
140
- }
141
- function saveApiKeyToEnv(apiKey) {
142
- const envVar = `NEXT_PUBLIC_FEEDBACKWIDGET_API_KEY=${apiKey}`;
143
- const envFiles = [".env.local", ".env"];
144
- for (const envFile of envFiles) {
145
- const envPath = path.join(process.cwd(), envFile);
146
- try {
147
- const content = fs.readFileSync(envPath, "utf-8");
148
- if (content.includes("NEXT_PUBLIC_FEEDBACKWIDGET_API_KEY=")) {
149
- const updated = content.replace(
150
- /NEXT_PUBLIC_FEEDBACKWIDGET_API_KEY=.*/,
151
- envVar
152
- );
153
- fs.writeFileSync(envPath, updated);
154
- } else {
155
- fs.appendFileSync(envPath, `
156
- ${envVar}
157
- `);
158
- }
159
- return envFile;
160
- } catch {
161
- continue;
162
- }
163
- }
164
- try {
165
- const envPath = path.join(process.cwd(), ".env.local");
166
- fs.writeFileSync(envPath, `${envVar}
167
- `);
168
- return ".env.local";
169
- } catch {
170
- return null;
171
- }
172
- }
173
120
  function successPage() {
174
121
  return `
175
122
  <!DOCTYPE html>
@@ -216,4 +163,86 @@ function errorPage(error) {
216
163
  </body>
217
164
  </html>`;
218
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
+ }
222
+ async function copyToClipboard(text) {
223
+ return new Promise((resolve, reject) => {
224
+ const platform = process.platform;
225
+ let cmd;
226
+ let args = [];
227
+ if (platform === "darwin") {
228
+ cmd = "pbcopy";
229
+ } else if (platform === "linux") {
230
+ cmd = "xclip";
231
+ args = ["-selection", "clipboard"];
232
+ } else if (platform === "win32") {
233
+ cmd = "clip";
234
+ } else {
235
+ reject(new Error("Unsupported platform"));
236
+ return;
237
+ }
238
+ const proc = spawn(cmd, args);
239
+ proc.stdin.write(text);
240
+ proc.stdin.end();
241
+ proc.on("close", (code) => {
242
+ if (code === 0) resolve();
243
+ else reject(new Error(`Clipboard failed with code ${code}`));
244
+ });
245
+ proc.on("error", reject);
246
+ });
247
+ }
219
248
  main().catch(console.error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stephenov/feedbackwidget",
3
- "version": "0.3.1",
3
+ "version": "0.3.5",
4
4
  "description": "Voice-first feedback widget with AI analysis, GitHub, and Slack integration",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",