@stephenov/feedbackwidget 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.
package/dist/cli.d.mts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.js ADDED
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/cli.ts
27
+ var import_http = __toESM(require("http"));
28
+ var import_open = __toESM(require("open"));
29
+ var import_crypto = require("crypto");
30
+ var import_fs = __toESM(require("fs"));
31
+ var import_path = __toESM(require("path"));
32
+ var API_BASE = "https://feedbackwidget-api.vercel.app";
33
+ var CLI_PORT = 9876;
34
+ async function main() {
35
+ const args = process.argv.slice(2);
36
+ const command = args[0];
37
+ if (command === "init") {
38
+ await initProject();
39
+ } else if (command === "whoami") {
40
+ await whoami();
41
+ } else {
42
+ showHelp();
43
+ }
44
+ }
45
+ async function initProject() {
46
+ 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
+ const state = (0, import_crypto.randomBytes)(16).toString("hex");
55
+ const apiKey = await new Promise((resolve, reject) => {
56
+ const server = import_http.default.createServer((req, res) => {
57
+ const url = new URL(req.url, `http://localhost:${CLI_PORT}`);
58
+ if (url.pathname === "/callback") {
59
+ const receivedState = url.searchParams.get("state");
60
+ const key = url.searchParams.get("key");
61
+ const error = url.searchParams.get("error");
62
+ if (error) {
63
+ res.writeHead(200, { "Content-Type": "text/html" });
64
+ res.end(errorPage(error));
65
+ server.close();
66
+ reject(new Error(error));
67
+ return;
68
+ }
69
+ if (receivedState !== state) {
70
+ res.writeHead(200, { "Content-Type": "text/html" });
71
+ res.end(errorPage("Invalid state - possible CSRF attack"));
72
+ server.close();
73
+ reject(new Error("Invalid state"));
74
+ return;
75
+ }
76
+ if (key) {
77
+ res.writeHead(200, { "Content-Type": "text/html" });
78
+ res.end(successPage());
79
+ server.close();
80
+ resolve(key);
81
+ }
82
+ }
83
+ });
84
+ server.listen(CLI_PORT, () => {
85
+ const authUrl = `${API_BASE}/cli/auth?state=${state}&port=${CLI_PORT}`;
86
+ console.log("Opening browser to authenticate...\n");
87
+ console.log(` If browser doesn't open, visit:
88
+ ${authUrl}
89
+ `);
90
+ (0, import_open.default)(authUrl);
91
+ });
92
+ setTimeout(() => {
93
+ server.close();
94
+ reject(new Error("Authentication timed out"));
95
+ }, 5 * 60 * 1e3);
96
+ });
97
+ saveApiKey(apiKey);
98
+ console.log("\u2705 Authenticated successfully!\n");
99
+ console.log(` API Key: ${apiKey.slice(0, 20)}...`);
100
+ console.log(" Saved to .feedbackwidgetrc\n");
101
+ console.log(" Add to your app:\n");
102
+ console.log(' import { FeedbackWidget } from "@stephenov/feedbackwidget"');
103
+ console.log(` <FeedbackWidget apiKey="${apiKey}" />
104
+ `);
105
+ }
106
+ async function whoami() {
107
+ const apiKey = getStoredApiKey();
108
+ if (!apiKey) {
109
+ console.log("\nNot logged in. Run: npx @stephenov/feedbackwidget init\n");
110
+ return;
111
+ }
112
+ try {
113
+ const res = await fetch(`${API_BASE}/api/v1/validate`, {
114
+ headers: { Authorization: `Bearer ${apiKey}` }
115
+ });
116
+ const data = await res.json();
117
+ if (data.valid) {
118
+ console.log(`
119
+ Logged in as: ${data.project?.name || "Unknown"}`);
120
+ console.log(`Plan: ${data.limits?.submissions || 100} submissions/month
121
+ `);
122
+ } else {
123
+ console.log("\nInvalid API key. Run: npx @stephenov/feedbackwidget init\n");
124
+ }
125
+ } catch {
126
+ console.log("\nCouldn't verify API key. Check your connection.\n");
127
+ }
128
+ }
129
+ function showHelp() {
130
+ console.log(`
131
+ \u{1F3A4} feedbackwidget CLI
132
+
133
+ Commands:
134
+ init Authenticate and get your API key
135
+ whoami Show current project info
136
+
137
+ Usage:
138
+ npx @stephenov/feedbackwidget init
139
+ npx @stephenov/feedbackwidget whoami
140
+ `);
141
+ }
142
+ function getStoredApiKey() {
143
+ const rcPath = import_path.default.join(process.cwd(), ".feedbackwidgetrc");
144
+ try {
145
+ const content = import_fs.default.readFileSync(rcPath, "utf-8");
146
+ const match = content.match(/FEEDBACKWIDGET_API_KEY=(.+)/);
147
+ return match ? match[1].trim() : null;
148
+ } catch {
149
+ return null;
150
+ }
151
+ }
152
+ function saveApiKey(apiKey) {
153
+ const rcPath = import_path.default.join(process.cwd(), ".feedbackwidgetrc");
154
+ import_fs.default.writeFileSync(rcPath, `FEEDBACKWIDGET_API_KEY=${apiKey}
155
+ `);
156
+ const gitignorePath = import_path.default.join(process.cwd(), ".gitignore");
157
+ try {
158
+ const gitignore = import_fs.default.readFileSync(gitignorePath, "utf-8");
159
+ if (!gitignore.includes(".feedbackwidgetrc")) {
160
+ import_fs.default.appendFileSync(gitignorePath, "\n.feedbackwidgetrc\n");
161
+ }
162
+ } catch {
163
+ }
164
+ }
165
+ function successPage() {
166
+ return `
167
+ <!DOCTYPE html>
168
+ <html>
169
+ <head>
170
+ <title>feedbackwidget - Authenticated</title>
171
+ <style>
172
+ body { font-family: -apple-system, sans-serif; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; background: #f5f5f5; }
173
+ .card { background: white; padding: 48px; border-radius: 16px; text-align: center; box-shadow: 0 4px 20px rgba(0,0,0,0.1); }
174
+ h1 { font-size: 24px; margin: 0 0 16px; }
175
+ p { color: #666; margin: 0; }
176
+ .emoji { font-size: 48px; margin-bottom: 24px; }
177
+ </style>
178
+ </head>
179
+ <body>
180
+ <div class="card">
181
+ <div class="emoji">\u2705</div>
182
+ <h1>Authenticated!</h1>
183
+ <p>You can close this window and return to your terminal.</p>
184
+ </div>
185
+ </body>
186
+ </html>`;
187
+ }
188
+ function errorPage(error) {
189
+ return `
190
+ <!DOCTYPE html>
191
+ <html>
192
+ <head>
193
+ <title>feedbackwidget - Error</title>
194
+ <style>
195
+ body { font-family: -apple-system, sans-serif; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; background: #f5f5f5; }
196
+ .card { background: white; padding: 48px; border-radius: 16px; text-align: center; box-shadow: 0 4px 20px rgba(0,0,0,0.1); }
197
+ h1 { font-size: 24px; margin: 0 0 16px; color: #e00; }
198
+ p { color: #666; margin: 0; }
199
+ .emoji { font-size: 48px; margin-bottom: 24px; }
200
+ </style>
201
+ </head>
202
+ <body>
203
+ <div class="card">
204
+ <div class="emoji">\u274C</div>
205
+ <h1>Authentication Failed</h1>
206
+ <p>${error}</p>
207
+ </div>
208
+ </body>
209
+ </html>`;
210
+ }
211
+ main().catch(console.error);
package/dist/cli.mjs ADDED
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import http from "http";
5
+ import open from "open";
6
+ import { randomBytes } from "crypto";
7
+ import fs from "fs";
8
+ import path from "path";
9
+ var API_BASE = "https://feedbackwidget-api.vercel.app";
10
+ var CLI_PORT = 9876;
11
+ async function main() {
12
+ const args = process.argv.slice(2);
13
+ const command = args[0];
14
+ if (command === "init") {
15
+ await initProject();
16
+ } else if (command === "whoami") {
17
+ await whoami();
18
+ } else {
19
+ showHelp();
20
+ }
21
+ }
22
+ async function initProject() {
23
+ 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
+ const state = randomBytes(16).toString("hex");
32
+ const apiKey = await new Promise((resolve, reject) => {
33
+ const server = http.createServer((req, res) => {
34
+ const url = new URL(req.url, `http://localhost:${CLI_PORT}`);
35
+ if (url.pathname === "/callback") {
36
+ const receivedState = url.searchParams.get("state");
37
+ const key = url.searchParams.get("key");
38
+ const error = url.searchParams.get("error");
39
+ if (error) {
40
+ res.writeHead(200, { "Content-Type": "text/html" });
41
+ res.end(errorPage(error));
42
+ server.close();
43
+ reject(new Error(error));
44
+ return;
45
+ }
46
+ if (receivedState !== state) {
47
+ res.writeHead(200, { "Content-Type": "text/html" });
48
+ res.end(errorPage("Invalid state - possible CSRF attack"));
49
+ server.close();
50
+ reject(new Error("Invalid state"));
51
+ return;
52
+ }
53
+ if (key) {
54
+ res.writeHead(200, { "Content-Type": "text/html" });
55
+ res.end(successPage());
56
+ server.close();
57
+ resolve(key);
58
+ }
59
+ }
60
+ });
61
+ server.listen(CLI_PORT, () => {
62
+ const authUrl = `${API_BASE}/cli/auth?state=${state}&port=${CLI_PORT}`;
63
+ console.log("Opening browser to authenticate...\n");
64
+ console.log(` If browser doesn't open, visit:
65
+ ${authUrl}
66
+ `);
67
+ open(authUrl);
68
+ });
69
+ setTimeout(() => {
70
+ server.close();
71
+ reject(new Error("Authentication timed out"));
72
+ }, 5 * 60 * 1e3);
73
+ });
74
+ saveApiKey(apiKey);
75
+ console.log("\u2705 Authenticated successfully!\n");
76
+ console.log(` API Key: ${apiKey.slice(0, 20)}...`);
77
+ console.log(" Saved to .feedbackwidgetrc\n");
78
+ console.log(" Add to your app:\n");
79
+ console.log(' import { FeedbackWidget } from "@stephenov/feedbackwidget"');
80
+ console.log(` <FeedbackWidget apiKey="${apiKey}" />
81
+ `);
82
+ }
83
+ async function whoami() {
84
+ const apiKey = getStoredApiKey();
85
+ if (!apiKey) {
86
+ console.log("\nNot logged in. Run: npx @stephenov/feedbackwidget init\n");
87
+ return;
88
+ }
89
+ try {
90
+ const res = await fetch(`${API_BASE}/api/v1/validate`, {
91
+ headers: { Authorization: `Bearer ${apiKey}` }
92
+ });
93
+ const data = await res.json();
94
+ if (data.valid) {
95
+ console.log(`
96
+ Logged in as: ${data.project?.name || "Unknown"}`);
97
+ console.log(`Plan: ${data.limits?.submissions || 100} submissions/month
98
+ `);
99
+ } else {
100
+ console.log("\nInvalid API key. Run: npx @stephenov/feedbackwidget init\n");
101
+ }
102
+ } catch {
103
+ console.log("\nCouldn't verify API key. Check your connection.\n");
104
+ }
105
+ }
106
+ function showHelp() {
107
+ console.log(`
108
+ \u{1F3A4} feedbackwidget CLI
109
+
110
+ Commands:
111
+ init Authenticate and get your API key
112
+ whoami Show current project info
113
+
114
+ Usage:
115
+ npx @stephenov/feedbackwidget init
116
+ npx @stephenov/feedbackwidget whoami
117
+ `);
118
+ }
119
+ function getStoredApiKey() {
120
+ const rcPath = path.join(process.cwd(), ".feedbackwidgetrc");
121
+ try {
122
+ const content = fs.readFileSync(rcPath, "utf-8");
123
+ const match = content.match(/FEEDBACKWIDGET_API_KEY=(.+)/);
124
+ return match ? match[1].trim() : null;
125
+ } catch {
126
+ return null;
127
+ }
128
+ }
129
+ function saveApiKey(apiKey) {
130
+ const rcPath = path.join(process.cwd(), ".feedbackwidgetrc");
131
+ fs.writeFileSync(rcPath, `FEEDBACKWIDGET_API_KEY=${apiKey}
132
+ `);
133
+ const gitignorePath = path.join(process.cwd(), ".gitignore");
134
+ try {
135
+ const gitignore = fs.readFileSync(gitignorePath, "utf-8");
136
+ if (!gitignore.includes(".feedbackwidgetrc")) {
137
+ fs.appendFileSync(gitignorePath, "\n.feedbackwidgetrc\n");
138
+ }
139
+ } catch {
140
+ }
141
+ }
142
+ function successPage() {
143
+ return `
144
+ <!DOCTYPE html>
145
+ <html>
146
+ <head>
147
+ <title>feedbackwidget - Authenticated</title>
148
+ <style>
149
+ body { font-family: -apple-system, sans-serif; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; background: #f5f5f5; }
150
+ .card { background: white; padding: 48px; border-radius: 16px; text-align: center; box-shadow: 0 4px 20px rgba(0,0,0,0.1); }
151
+ h1 { font-size: 24px; margin: 0 0 16px; }
152
+ p { color: #666; margin: 0; }
153
+ .emoji { font-size: 48px; margin-bottom: 24px; }
154
+ </style>
155
+ </head>
156
+ <body>
157
+ <div class="card">
158
+ <div class="emoji">\u2705</div>
159
+ <h1>Authenticated!</h1>
160
+ <p>You can close this window and return to your terminal.</p>
161
+ </div>
162
+ </body>
163
+ </html>`;
164
+ }
165
+ function errorPage(error) {
166
+ return `
167
+ <!DOCTYPE html>
168
+ <html>
169
+ <head>
170
+ <title>feedbackwidget - Error</title>
171
+ <style>
172
+ body { font-family: -apple-system, sans-serif; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; background: #f5f5f5; }
173
+ .card { background: white; padding: 48px; border-radius: 16px; text-align: center; box-shadow: 0 4px 20px rgba(0,0,0,0.1); }
174
+ h1 { font-size: 24px; margin: 0 0 16px; color: #e00; }
175
+ p { color: #666; margin: 0; }
176
+ .emoji { font-size: 48px; margin-bottom: 24px; }
177
+ </style>
178
+ </head>
179
+ <body>
180
+ <div class="card">
181
+ <div class="emoji">\u274C</div>
182
+ <h1>Authentication Failed</h1>
183
+ <p>${error}</p>
184
+ </div>
185
+ </body>
186
+ </html>`;
187
+ }
188
+ main().catch(console.error);
package/dist/index.js CHANGED
@@ -632,7 +632,7 @@ function VoiceRecorder({
632
632
  }
633
633
 
634
634
  // src/api/client.ts
635
- var API_BASE = "https://api.feedbackwidget.dev/v1";
635
+ var API_BASE = "https://feedbackwidget-api.vercel.app/api/v1";
636
636
  var FeedbackApiClient = class {
637
637
  constructor(apiKey, baseUrl) {
638
638
  this.apiKey = apiKey;
package/dist/index.mjs CHANGED
@@ -587,7 +587,7 @@ function VoiceRecorder({
587
587
  }
588
588
 
589
589
  // src/api/client.ts
590
- var API_BASE = "https://api.feedbackwidget.dev/v1";
590
+ var API_BASE = "https://feedbackwidget-api.vercel.app/api/v1";
591
591
  var FeedbackApiClient = class {
592
592
  constructor(apiKey, baseUrl) {
593
593
  this.apiKey = apiKey;
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "@stephenov/feedbackwidget",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
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",
7
7
  "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "feedbackwidget": "./dist/cli.js"
10
+ },
8
11
  "exports": {
9
12
  ".": {
10
13
  "types": "./dist/index.d.ts",
@@ -17,8 +20,8 @@
17
20
  "dist"
18
21
  ],
19
22
  "scripts": {
20
- "build": "tsup src/index.ts --format cjs,esm --dts --clean",
21
- "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
23
+ "build": "tsup src/index.ts src/cli.ts --format cjs,esm --dts --clean",
24
+ "dev": "tsup src/index.ts src/cli.ts --format cjs,esm --dts --watch",
22
25
  "lint": "eslint src/",
23
26
  "clean": "rm -rf dist"
24
27
  },
@@ -26,7 +29,11 @@
26
29
  "react": ">=18.0.0",
27
30
  "react-dom": ">=18.0.0"
28
31
  },
32
+ "dependencies": {
33
+ "open": "^10.0.0"
34
+ },
29
35
  "devDependencies": {
36
+ "@types/node": "^20.0.0",
30
37
  "@types/react": "^18.2.0",
31
38
  "@types/react-dom": "^18.2.0",
32
39
  "react": "^18.2.0",
@@ -42,12 +49,13 @@
42
49
  "react",
43
50
  "github",
44
51
  "slack",
45
- "ai"
52
+ "ai",
53
+ "cli"
46
54
  ],
47
55
  "license": "MIT",
48
56
  "repository": {
49
57
  "type": "git",
50
- "url": "https://github.com/outcomeview/feedbackwidget"
58
+ "url": "https://github.com/stephennewman/feedbackwidget"
51
59
  },
52
- "homepage": "https://feedbackwidget.dev"
60
+ "homepage": "https://feedbackwidget-api.vercel.app"
53
61
  }