feedbackbasket-cli 0.4.0 → 0.6.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/README.md +34 -2
- package/dist/src/auth/login.js +6 -2
- package/dist/src/client.d.ts +4 -4
- package/dist/src/commands/auth.js +2 -2
- package/dist/src/commands/feedback.js +18 -0
- package/dist/src/commands/widget.js +245 -4
- package/dist/src/help.js +1 -0
- package/dist/src/types.d.ts +53 -0
- package/dist/src/version.d.ts +2 -2
- package/dist/src/version.js +1 -1
- package/package.json +1 -1
- package/skills/feedbackbasket/SKILL.md +13 -2
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ feedbackbasket login
|
|
|
16
16
|
# Or use a token directly (for CI/headless)
|
|
17
17
|
feedbackbasket login --token fb_cli_your_token_here
|
|
18
18
|
|
|
19
|
-
# Remote server
|
|
19
|
+
# Remote server flow: use when localhost browser callbacks cannot reach the CLI
|
|
20
20
|
feedbackbasket login --manual
|
|
21
21
|
|
|
22
22
|
# Start exploring
|
|
@@ -51,7 +51,7 @@ feedbackbasket setup claude
|
|
|
51
51
|
|
|
52
52
|
```bash
|
|
53
53
|
feedbackbasket login # Browser OAuth flow (alias for auth login)
|
|
54
|
-
feedbackbasket login --manual #
|
|
54
|
+
feedbackbasket login --manual # No localhost browser callback (remote servers)
|
|
55
55
|
feedbackbasket login --token <token> # Use an existing CLI token (CI / scripts)
|
|
56
56
|
feedbackbasket logout # Clear credentials (alias for auth logout)
|
|
57
57
|
feedbackbasket auth status # Show auth state, scope, default project
|
|
@@ -121,11 +121,43 @@ feedbackbasket widget settings myapp
|
|
|
121
121
|
feedbackbasket widget settings myapp --color "#22c55e" --label "Send Feedback"
|
|
122
122
|
feedbackbasket widget settings myapp --position bottom-left --display modal
|
|
123
123
|
feedbackbasket widget settings myapp --email-required --intro "How can we improve?"
|
|
124
|
+
feedbackbasket widget settings myapp --button-radius 10 --button-size regular
|
|
125
|
+
feedbackbasket widget settings myapp --show-email --allow-attachments --guided
|
|
126
|
+
|
|
127
|
+
# Configure guided feedback types and follow-up questions
|
|
128
|
+
feedbackbasket widget flow myapp
|
|
129
|
+
feedbackbasket widget flow myapp --enable
|
|
130
|
+
feedbackbasket widget flow myapp --reset-default --enable
|
|
131
|
+
feedbackbasket widget flow myapp --config ./feedback-flow.json
|
|
124
132
|
|
|
125
133
|
# Get embed code (ready to paste into your HTML)
|
|
126
134
|
feedbackbasket widget script myapp
|
|
127
135
|
```
|
|
128
136
|
|
|
137
|
+
`widget flow --config` accepts either a `feedbackFlow` object or a JSON object with a `feedbackFlow` key. V1 supports guided mode with `text`, `textarea`, and `single_choice` follow-up questions.
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"enabled": true,
|
|
142
|
+
"mode": "guided",
|
|
143
|
+
"types": [
|
|
144
|
+
{
|
|
145
|
+
"id": "bug",
|
|
146
|
+
"emoji": "🐞",
|
|
147
|
+
"label": "Bug report",
|
|
148
|
+
"description": "Something is broken or not working",
|
|
149
|
+
"questions": [
|
|
150
|
+
{
|
|
151
|
+
"id": "steps",
|
|
152
|
+
"label": "What steps can reproduce it?",
|
|
153
|
+
"type": "textarea"
|
|
154
|
+
}
|
|
155
|
+
]
|
|
156
|
+
}
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
129
161
|
### Team
|
|
130
162
|
|
|
131
163
|
```bash
|
package/dist/src/auth/login.js
CHANGED
|
@@ -108,7 +108,8 @@ export async function browserLogin(baseUrl, scope = 'read') {
|
|
|
108
108
|
console.log(` ${brand.primary(authorizeUrl)}`);
|
|
109
109
|
console.log();
|
|
110
110
|
console.log(` ${brand.muted('Waiting for authentication...')}`);
|
|
111
|
-
console.log(` ${brand.muted('
|
|
111
|
+
console.log(` ${brand.muted('If this machine cannot receive the localhost browser callback, use:')}`);
|
|
112
|
+
console.log(` ${brand.command('feedbackbasket login --manual')}`);
|
|
112
113
|
console.log();
|
|
113
114
|
open(authorizeUrl).catch(() => {
|
|
114
115
|
// Browser open failed; user will use the URL manually.
|
|
@@ -121,10 +122,13 @@ export async function manualLogin(baseUrl, scope = 'read') {
|
|
|
121
122
|
console.log();
|
|
122
123
|
console.log(` ${logo()} CLI`);
|
|
123
124
|
console.log();
|
|
124
|
-
console.log(` ${brand.primary('Open this URL
|
|
125
|
+
console.log(` ${brand.primary('Open this URL on any machine with a browser:')}`);
|
|
125
126
|
console.log();
|
|
126
127
|
console.log(` ${brand.primary(authorizeUrl)}`);
|
|
127
128
|
console.log();
|
|
129
|
+
console.log(` ${brand.muted('Use this when a remote server cannot receive the localhost browser callback.')}`);
|
|
130
|
+
console.log(` ${brand.muted('The CLI still needs outbound HTTPS access to verify and use the token.')}`);
|
|
131
|
+
console.log();
|
|
128
132
|
console.log(` ${brand.muted('After approving access, paste the token shown in your browser.')}`);
|
|
129
133
|
console.log();
|
|
130
134
|
open(authorizeUrl).catch(() => {
|
package/dist/src/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ProjectsResponse, FeedbackResponse, BugReportsResponse, FeedbackParams, BugReportParams, UserProfile, Project, Feedback } from './types.js';
|
|
1
|
+
import type { ProjectsResponse, FeedbackResponse, BugReportsResponse, FeedbackParams, BugReportParams, UserProfile, Project, Feedback, WidgetSettings } from './types.js';
|
|
2
2
|
export declare class FeedbackBasketClient {
|
|
3
3
|
private http;
|
|
4
4
|
constructor(token: string, baseUrl: string);
|
|
@@ -37,12 +37,12 @@ export declare class FeedbackBasketClient {
|
|
|
37
37
|
getWidgetSettings(projectId: string): Promise<{
|
|
38
38
|
projectId: string;
|
|
39
39
|
projectName: string;
|
|
40
|
-
settings:
|
|
40
|
+
settings: WidgetSettings;
|
|
41
41
|
}>;
|
|
42
|
-
updateWidgetSettings(projectId: string, settings:
|
|
42
|
+
updateWidgetSettings(projectId: string, settings: Partial<WidgetSettings>): Promise<{
|
|
43
43
|
projectId: string;
|
|
44
44
|
projectName: string;
|
|
45
|
-
settings:
|
|
45
|
+
settings: WidgetSettings;
|
|
46
46
|
}>;
|
|
47
47
|
getWidgetScript(projectId: string): Promise<{
|
|
48
48
|
projectId: string;
|
|
@@ -18,7 +18,7 @@ export function createAuthCommand(getWriter) {
|
|
|
18
18
|
.command('login')
|
|
19
19
|
.description('Authenticate with FeedbackBasket')
|
|
20
20
|
.option('--token <token>', 'Use an API token directly (for CI/headless)')
|
|
21
|
-
.option('--manual', '
|
|
21
|
+
.option('--manual', 'Authenticate without a localhost browser callback')
|
|
22
22
|
.option('--scope <scope>', 'Access scope: read or full', 'full')
|
|
23
23
|
.action(async (opts) => {
|
|
24
24
|
const writer = getWriter();
|
|
@@ -299,7 +299,7 @@ export function createLoginCommand(getWriter) {
|
|
|
299
299
|
return new Command('login')
|
|
300
300
|
.description('Authenticate with FeedbackBasket (alias for auth login)')
|
|
301
301
|
.option('--token <token>', 'Use an API token directly (for CI/headless)')
|
|
302
|
-
.option('--manual', '
|
|
302
|
+
.option('--manual', 'Authenticate without a localhost browser callback')
|
|
303
303
|
.option('--scope <scope>', 'Access scope: read or full', 'full')
|
|
304
304
|
.action(async (opts) => {
|
|
305
305
|
// Delegate to auth login by re-parsing
|
|
@@ -171,6 +171,7 @@ function renderFeedbackDetail(item) {
|
|
|
171
171
|
const fields = [
|
|
172
172
|
['Status', item.status],
|
|
173
173
|
['Category', item.category],
|
|
174
|
+
['Feedback Type', formatFeedbackType(item)],
|
|
174
175
|
['Sentiment', item.sentiment],
|
|
175
176
|
['Priority', item.aiPriorityScore != null ? String(item.aiPriorityScore) : null],
|
|
176
177
|
['Email', item.email],
|
|
@@ -190,6 +191,15 @@ function renderFeedbackDetail(item) {
|
|
|
190
191
|
console.log();
|
|
191
192
|
console.log(brand.bold('Content:'));
|
|
192
193
|
console.log(` ${item.content}`);
|
|
194
|
+
if (item.followUpAnswers && item.followUpAnswers.length > 0) {
|
|
195
|
+
console.log();
|
|
196
|
+
console.log(brand.bold('Follow-up Answers:'));
|
|
197
|
+
for (const answer of item.followUpAnswers) {
|
|
198
|
+
const value = answer.value?.trim() || brand.muted('(skipped)');
|
|
199
|
+
console.log(` ${brand.bold(answer.label)}`);
|
|
200
|
+
console.log(` ${value}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
193
203
|
if (item.aiSummary) {
|
|
194
204
|
console.log();
|
|
195
205
|
console.log(brand.bold('AI Summary:'));
|
|
@@ -209,3 +219,11 @@ function renderFeedbackDetail(item) {
|
|
|
209
219
|
}
|
|
210
220
|
}
|
|
211
221
|
}
|
|
222
|
+
function formatFeedbackType(item) {
|
|
223
|
+
if (!item.feedbackType)
|
|
224
|
+
return undefined;
|
|
225
|
+
const label = item.feedbackType.label || item.feedbackType.id;
|
|
226
|
+
if (!label)
|
|
227
|
+
return undefined;
|
|
228
|
+
return item.feedbackType.emoji ? `${item.feedbackType.emoji} ${label}` : label;
|
|
229
|
+
}
|
|
@@ -1,10 +1,49 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
2
3
|
import { FeedbackBasketClient } from '../client.js';
|
|
3
4
|
import { AuthManager } from '../auth/manager.js';
|
|
4
5
|
import { loadConfig } from '../config/config.js';
|
|
5
6
|
import { errAuth, errUsage } from '../output/errors.js';
|
|
6
7
|
import { brand, divider } from '../output/theme.js';
|
|
7
8
|
import { resolveProject } from '../resolve.js';
|
|
9
|
+
const DEFAULT_FEEDBACK_FLOW = {
|
|
10
|
+
enabled: false,
|
|
11
|
+
mode: 'guided',
|
|
12
|
+
types: [
|
|
13
|
+
{
|
|
14
|
+
id: 'bug',
|
|
15
|
+
emoji: '🐞',
|
|
16
|
+
label: 'Bug report',
|
|
17
|
+
description: 'Something is broken or not working',
|
|
18
|
+
questions: [
|
|
19
|
+
{ id: 'steps', label: 'What steps can reproduce it?', type: 'textarea', placeholder: 'Tell us what you did before the issue happened.' },
|
|
20
|
+
{ id: 'expected', label: 'What did you expect to happen?', type: 'textarea', placeholder: 'Describe the result you expected.' },
|
|
21
|
+
{ id: 'urgency', label: 'How urgent is this?', type: 'single_choice', options: ['Low', 'Medium', 'High'] },
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: 'feature',
|
|
26
|
+
emoji: '💡',
|
|
27
|
+
label: 'Feature request',
|
|
28
|
+
description: 'Suggest an idea or improvement',
|
|
29
|
+
questions: [
|
|
30
|
+
{ id: 'problem', label: 'What problem are you trying to solve?', type: 'textarea', placeholder: 'Share the job this feature would help with.' },
|
|
31
|
+
{ id: 'benefit', label: 'Who would benefit from this?', type: 'text', placeholder: 'For example: admins, customers, teammates.' },
|
|
32
|
+
{ id: 'importance', label: 'How important is this?', type: 'single_choice', options: ['Nice to have', 'Important', 'Critical'] },
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'general',
|
|
37
|
+
emoji: '💬',
|
|
38
|
+
label: 'General feedback',
|
|
39
|
+
description: 'Share thoughts, praise, or anything else',
|
|
40
|
+
questions: [
|
|
41
|
+
{ id: 'context', label: 'What made you want to share this?', type: 'textarea', placeholder: 'Add any helpful context.' },
|
|
42
|
+
{ id: 'sentiment', label: 'How are you feeling about it?', type: 'single_choice', options: ['Happy', 'Neutral', 'Frustrated'] },
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
};
|
|
8
47
|
export function createWidgetCommand(getWriter) {
|
|
9
48
|
const widget = new Command('widget')
|
|
10
49
|
.description('Manage feedback widget');
|
|
@@ -14,23 +53,44 @@ export function createWidgetCommand(getWriter) {
|
|
|
14
53
|
.description('View or update widget settings')
|
|
15
54
|
.option('--color <hex>', 'Button color (e.g. #22c55e)')
|
|
16
55
|
.option('--label <text>', 'Button label')
|
|
17
|
-
.option('--position <pos>', 'Widget position (bottom-right, bottom-left,
|
|
56
|
+
.option('--position <pos>', 'Widget position (bottom-right, bottom-left, middle-right-edge, middle-left-edge, bottom-right-edge, bottom-left-edge)')
|
|
18
57
|
.option('--intro <text>', 'Intro message shown in the widget')
|
|
19
58
|
.option('--success <text>', 'Success message after submission')
|
|
20
|
-
.option('--trigger <mode>', 'Trigger mode (floating,
|
|
21
|
-
.option('--display <mode>', 'Display mode (modal,
|
|
59
|
+
.option('--trigger <mode>', 'Trigger mode (floating, inline)')
|
|
60
|
+
.option('--display <mode>', 'Display mode (modal, popup)')
|
|
61
|
+
.option('--button-radius <px>', 'Button radius in pixels')
|
|
62
|
+
.option('--button-size <size>', 'Button size (mini, regular)')
|
|
63
|
+
.option('--icon <value>', 'Icon value (emoji or svg:chat-bubble-bottom-center-text)')
|
|
22
64
|
.option('--email-required', 'Require email from submitters')
|
|
23
65
|
.option('--no-email-required', 'Make email optional')
|
|
66
|
+
.option('--show-email', 'Show the email field')
|
|
67
|
+
.option('--no-show-email', 'Hide the email field')
|
|
68
|
+
.option('--allow-attachments', 'Allow image attachments')
|
|
69
|
+
.option('--no-allow-attachments', 'Disable image attachments')
|
|
24
70
|
.option('--icon-only', 'Show only the icon, no label')
|
|
25
71
|
.option('--no-icon-only', 'Show both icon and label')
|
|
72
|
+
.option('--show-icon', 'Show an icon next to the label')
|
|
73
|
+
.option('--no-show-icon', 'Hide the icon')
|
|
74
|
+
.option('--show-branding', 'Show FeedbackBasket branding')
|
|
75
|
+
.option('--no-show-branding', 'Hide FeedbackBasket branding when plan allows it')
|
|
76
|
+
.option('--z-index <value>', 'Widget z-index')
|
|
77
|
+
.option('--guided', 'Enable guided feedback types')
|
|
78
|
+
.option('--disable-guided', 'Disable guided feedback types')
|
|
26
79
|
.action(async (projectArg, opts) => {
|
|
27
80
|
const writer = getWriter();
|
|
28
81
|
const client = requireClient();
|
|
29
82
|
const projectId = await resolveProjectId(client, projectArg);
|
|
30
83
|
const hasUpdates = opts.color || opts.label || opts.position || opts.intro ||
|
|
31
84
|
opts.success || opts.trigger || opts.display ||
|
|
32
|
-
opts.
|
|
85
|
+
opts.buttonRadius || opts.buttonSize || opts.icon ||
|
|
86
|
+
opts.emailRequired !== undefined || opts.showEmail !== undefined ||
|
|
87
|
+
opts.allowAttachments !== undefined || opts.iconOnly !== undefined ||
|
|
88
|
+
opts.showIcon !== undefined || opts.showBranding !== undefined ||
|
|
89
|
+
opts.zIndex || opts.guided || opts.disableGuided;
|
|
33
90
|
if (hasUpdates) {
|
|
91
|
+
if (opts.guided && opts.disableGuided) {
|
|
92
|
+
throw errUsage('Choose either --guided or --disable-guided, not both');
|
|
93
|
+
}
|
|
34
94
|
// Update mode
|
|
35
95
|
const settings = {};
|
|
36
96
|
if (opts.color)
|
|
@@ -47,10 +107,33 @@ export function createWidgetCommand(getWriter) {
|
|
|
47
107
|
settings.triggerMode = opts.trigger;
|
|
48
108
|
if (opts.display)
|
|
49
109
|
settings.displayMode = opts.display;
|
|
110
|
+
if (opts.buttonRadius)
|
|
111
|
+
settings.buttonRadius = parseInt(opts.buttonRadius, 10);
|
|
112
|
+
if (opts.buttonSize)
|
|
113
|
+
settings.buttonSize = opts.buttonSize;
|
|
114
|
+
if (opts.icon)
|
|
115
|
+
settings.icon = opts.icon;
|
|
50
116
|
if (opts.emailRequired !== undefined)
|
|
51
117
|
settings.emailRequired = opts.emailRequired;
|
|
118
|
+
if (opts.showEmail !== undefined)
|
|
119
|
+
settings.showEmailField = opts.showEmail;
|
|
120
|
+
if (opts.allowAttachments !== undefined)
|
|
121
|
+
settings.allowAttachments = opts.allowAttachments;
|
|
52
122
|
if (opts.iconOnly !== undefined)
|
|
53
123
|
settings.iconOnly = opts.iconOnly;
|
|
124
|
+
if (opts.showIcon !== undefined)
|
|
125
|
+
settings.showIcon = opts.showIcon;
|
|
126
|
+
if (opts.showBranding !== undefined)
|
|
127
|
+
settings.showBranding = opts.showBranding;
|
|
128
|
+
if (opts.zIndex)
|
|
129
|
+
settings.zIndex = parseInt(opts.zIndex, 10);
|
|
130
|
+
if (opts.guided || opts.disableGuided) {
|
|
131
|
+
const current = await client.getWidgetSettings(projectId);
|
|
132
|
+
settings.feedbackFlow = {
|
|
133
|
+
...(current.settings.feedbackFlow ?? DEFAULT_FEEDBACK_FLOW),
|
|
134
|
+
enabled: Boolean(opts.guided),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
54
137
|
const result = await client.updateWidgetSettings(projectId, settings);
|
|
55
138
|
if (!writer.isMachineOutput()) {
|
|
56
139
|
console.log(` ${brand.success('✓')} Widget settings updated for ${brand.bold(result.projectName)}`);
|
|
@@ -61,6 +144,7 @@ export function createWidgetCommand(getWriter) {
|
|
|
61
144
|
breadcrumbs: [
|
|
62
145
|
{ action: 'Get embed code', cmd: `feedbackbasket widget script ${projectId}` },
|
|
63
146
|
{ action: 'View settings', cmd: `feedbackbasket widget settings ${projectId}` },
|
|
147
|
+
{ action: 'Configure guided questions', cmd: `feedbackbasket widget flow ${projectId} --enable` },
|
|
64
148
|
],
|
|
65
149
|
});
|
|
66
150
|
}
|
|
@@ -74,10 +158,71 @@ export function createWidgetCommand(getWriter) {
|
|
|
74
158
|
summary: `Widget settings for "${result.projectName}"`,
|
|
75
159
|
breadcrumbs: [
|
|
76
160
|
{ action: 'Update color', cmd: `feedbackbasket widget settings ${projectId} --color "#22c55e"` },
|
|
161
|
+
{ action: 'Enable guided flow', cmd: `feedbackbasket widget flow ${projectId} --enable` },
|
|
162
|
+
{ action: 'Get embed code', cmd: `feedbackbasket widget script ${projectId}` },
|
|
163
|
+
],
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
// --- widget flow ---
|
|
168
|
+
widget
|
|
169
|
+
.command('flow [project]')
|
|
170
|
+
.description('View or update guided feedback types and follow-up questions')
|
|
171
|
+
.option('--enable', 'Enable guided feedback flow')
|
|
172
|
+
.option('--disable', 'Disable guided feedback flow')
|
|
173
|
+
.option('--reset-default', 'Reset guided flow to the default Bug, Feature, and General templates')
|
|
174
|
+
.option('--config <path>', 'Path to a JSON file containing a feedbackFlow object')
|
|
175
|
+
.action(async (projectArg, opts) => {
|
|
176
|
+
const writer = getWriter();
|
|
177
|
+
const client = requireClient();
|
|
178
|
+
const projectId = await resolveProjectId(client, projectArg);
|
|
179
|
+
const hasUpdates = opts.enable || opts.disable || opts.resetDefault || opts.config;
|
|
180
|
+
if (hasUpdates) {
|
|
181
|
+
if (opts.enable && opts.disable) {
|
|
182
|
+
throw errUsage('Choose either --enable or --disable, not both');
|
|
183
|
+
}
|
|
184
|
+
const current = await client.getWidgetSettings(projectId);
|
|
185
|
+
let feedbackFlow = current.settings.feedbackFlow ?? DEFAULT_FEEDBACK_FLOW;
|
|
186
|
+
if (opts.resetDefault) {
|
|
187
|
+
feedbackFlow = cloneDefaultFeedbackFlow();
|
|
188
|
+
}
|
|
189
|
+
if (opts.config) {
|
|
190
|
+
feedbackFlow = parseFeedbackFlowConfig(opts.config);
|
|
191
|
+
}
|
|
192
|
+
if (opts.enable)
|
|
193
|
+
feedbackFlow = { ...feedbackFlow, enabled: true };
|
|
194
|
+
if (opts.disable)
|
|
195
|
+
feedbackFlow = { ...feedbackFlow, enabled: false };
|
|
196
|
+
const result = await client.updateWidgetSettings(projectId, { feedbackFlow });
|
|
197
|
+
if (!writer.isMachineOutput()) {
|
|
198
|
+
console.log(` ${brand.success('✓')} Guided feedback flow updated for ${brand.bold(result.projectName)}`);
|
|
199
|
+
console.log();
|
|
200
|
+
renderFeedbackFlow(result.settings.feedbackFlow);
|
|
201
|
+
}
|
|
202
|
+
writer.ok(result.settings.feedbackFlow, {
|
|
203
|
+
summary: `Updated guided feedback flow for "${result.projectName}"`,
|
|
204
|
+
breadcrumbs: [
|
|
205
|
+
{ action: 'View widget settings', cmd: `feedbackbasket widget settings ${projectId}` },
|
|
77
206
|
{ action: 'Get embed code', cmd: `feedbackbasket widget script ${projectId}` },
|
|
78
207
|
],
|
|
79
208
|
});
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const result = await client.getWidgetSettings(projectId);
|
|
212
|
+
if (!writer.isMachineOutput()) {
|
|
213
|
+
console.log(brand.bold(`Guided feedback flow — ${result.projectName}`));
|
|
214
|
+
console.log(divider(40));
|
|
215
|
+
console.log();
|
|
216
|
+
renderFeedbackFlow(result.settings.feedbackFlow);
|
|
80
217
|
}
|
|
218
|
+
writer.ok(result.settings.feedbackFlow ?? DEFAULT_FEEDBACK_FLOW, {
|
|
219
|
+
summary: `Guided feedback flow for "${result.projectName}"`,
|
|
220
|
+
breadcrumbs: [
|
|
221
|
+
{ action: 'Enable guided flow', cmd: `feedbackbasket widget flow ${projectId} --enable` },
|
|
222
|
+
{ action: 'Reset templates', cmd: `feedbackbasket widget flow ${projectId} --reset-default --enable` },
|
|
223
|
+
{ action: 'Apply JSON config', cmd: `feedbackbasket widget flow ${projectId} --config ./feedback-flow.json` },
|
|
224
|
+
],
|
|
225
|
+
});
|
|
81
226
|
});
|
|
82
227
|
// --- widget script ---
|
|
83
228
|
widget
|
|
@@ -127,6 +272,66 @@ async function resolveProjectId(client, projectArg) {
|
|
|
127
272
|
return config.defaultProject;
|
|
128
273
|
throw errUsage('Project is required. Pass a project name/ID or set a default.', 'feedbackbasket widget settings <project> or feedbackbasket config set defaultProject <id>');
|
|
129
274
|
}
|
|
275
|
+
function cloneDefaultFeedbackFlow() {
|
|
276
|
+
return JSON.parse(JSON.stringify(DEFAULT_FEEDBACK_FLOW));
|
|
277
|
+
}
|
|
278
|
+
function parseFeedbackFlowConfig(path) {
|
|
279
|
+
const raw = readFileSync(path, 'utf8');
|
|
280
|
+
const parsed = JSON.parse(raw);
|
|
281
|
+
const candidate = isRecord(parsed) && isRecord(parsed.feedbackFlow)
|
|
282
|
+
? parsed.feedbackFlow
|
|
283
|
+
: parsed;
|
|
284
|
+
if (!isRecord(candidate) || !Array.isArray(candidate.types)) {
|
|
285
|
+
throw errUsage('Feedback flow config must be a feedbackFlow object with a types array');
|
|
286
|
+
}
|
|
287
|
+
return {
|
|
288
|
+
enabled: typeof candidate.enabled === 'boolean' ? candidate.enabled : true,
|
|
289
|
+
mode: 'guided',
|
|
290
|
+
types: candidate.types.map((type, index) => {
|
|
291
|
+
if (!isRecord(type))
|
|
292
|
+
throw errUsage(`Feedback type at index ${index} must be an object`);
|
|
293
|
+
const id = stringValue(type.id) || `type-${index + 1}`;
|
|
294
|
+
const label = stringValue(type.label);
|
|
295
|
+
if (!label)
|
|
296
|
+
throw errUsage(`Feedback type "${id}" is missing label`);
|
|
297
|
+
return {
|
|
298
|
+
id,
|
|
299
|
+
emoji: stringValue(type.emoji) || '💬',
|
|
300
|
+
label,
|
|
301
|
+
description: stringValue(type.description),
|
|
302
|
+
questions: Array.isArray(type.questions)
|
|
303
|
+
? type.questions.map((question, qIndex) => {
|
|
304
|
+
if (!isRecord(question))
|
|
305
|
+
throw errUsage(`Question ${qIndex + 1} in "${id}" must be an object`);
|
|
306
|
+
const questionId = stringValue(question.id) || `question-${qIndex + 1}`;
|
|
307
|
+
const questionLabel = stringValue(question.label);
|
|
308
|
+
const questionType = stringValue(question.type);
|
|
309
|
+
if (!questionLabel)
|
|
310
|
+
throw errUsage(`Question "${questionId}" in "${id}" is missing label`);
|
|
311
|
+
if (!['text', 'textarea', 'single_choice'].includes(questionType)) {
|
|
312
|
+
throw errUsage(`Question "${questionId}" type must be text, textarea, or single_choice`);
|
|
313
|
+
}
|
|
314
|
+
return {
|
|
315
|
+
id: questionId,
|
|
316
|
+
label: questionLabel,
|
|
317
|
+
type: questionType,
|
|
318
|
+
placeholder: stringValue(question.placeholder) || undefined,
|
|
319
|
+
options: Array.isArray(question.options)
|
|
320
|
+
? question.options.map(String).filter(Boolean)
|
|
321
|
+
: undefined,
|
|
322
|
+
};
|
|
323
|
+
})
|
|
324
|
+
: [],
|
|
325
|
+
};
|
|
326
|
+
}),
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function isRecord(value) {
|
|
330
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
331
|
+
}
|
|
332
|
+
function stringValue(value) {
|
|
333
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
334
|
+
}
|
|
130
335
|
function renderWidgetSettings(projectName, settings) {
|
|
131
336
|
console.log(brand.bold(`Widget settings — ${projectName}`));
|
|
132
337
|
console.log(divider(40));
|
|
@@ -135,12 +340,17 @@ function renderWidgetSettings(projectName, settings) {
|
|
|
135
340
|
['Button Color', String(settings.buttonColor ?? '')],
|
|
136
341
|
['Button Label', String(settings.buttonLabel ?? '')],
|
|
137
342
|
['Button Radius', String(settings.buttonRadius ?? '')],
|
|
343
|
+
['Button Size', String(settings.buttonSize ?? '')],
|
|
344
|
+
['Icon', String(settings.icon ?? '')],
|
|
138
345
|
['Icon Only', String(settings.iconOnly ?? false)],
|
|
139
346
|
['Show Icon', String(settings.showIcon ?? true)],
|
|
140
347
|
['Position', String(settings.position ?? '')],
|
|
141
348
|
['Trigger Mode', String(settings.triggerMode ?? '')],
|
|
142
349
|
['Display Mode', String(settings.displayMode ?? '')],
|
|
350
|
+
['Show Email', String(settings.showEmailField ?? true)],
|
|
143
351
|
['Email Required', String(settings.emailRequired ?? false)],
|
|
352
|
+
['Attachments', String(settings.allowAttachments ?? true)],
|
|
353
|
+
['Guided Flow', String(settings.feedbackFlow?.enabled ?? false)],
|
|
144
354
|
['Intro Message', String(settings.introMessage ?? '')],
|
|
145
355
|
['Success Message', String(settings.successMessage ?? '')],
|
|
146
356
|
['Z-Index', String(settings.zIndex ?? '')],
|
|
@@ -150,4 +360,35 @@ function renderWidgetSettings(projectName, settings) {
|
|
|
150
360
|
console.log(` ${brand.label(label.padEnd(18))} ${value}`);
|
|
151
361
|
}
|
|
152
362
|
console.log();
|
|
363
|
+
if (settings.feedbackFlow?.enabled) {
|
|
364
|
+
renderFeedbackFlow(settings.feedbackFlow);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
function renderFeedbackFlow(flow) {
|
|
368
|
+
const feedbackFlow = flow ?? DEFAULT_FEEDBACK_FLOW;
|
|
369
|
+
console.log(` ${brand.label('Enabled'.padEnd(18))} ${feedbackFlow.enabled}`);
|
|
370
|
+
console.log(` ${brand.label('Mode'.padEnd(18))} ${feedbackFlow.mode}`);
|
|
371
|
+
console.log();
|
|
372
|
+
if (feedbackFlow.types.length === 0) {
|
|
373
|
+
console.log(` ${brand.muted('No feedback types configured')}`);
|
|
374
|
+
console.log();
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
for (const type of feedbackFlow.types) {
|
|
378
|
+
const heading = `${type.emoji ? `${type.emoji} ` : ''}${type.label} (${type.id})`;
|
|
379
|
+
console.log(` ${brand.bold(heading)}`);
|
|
380
|
+
if (type.description) {
|
|
381
|
+
console.log(` ${brand.muted(type.description)}`);
|
|
382
|
+
}
|
|
383
|
+
if (type.questions.length === 0) {
|
|
384
|
+
console.log(` ${brand.muted('No follow-up questions')}`);
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
for (const question of type.questions) {
|
|
388
|
+
const options = question.options?.length ? ` [${question.options.join(', ')}]` : '';
|
|
389
|
+
console.log(` - ${question.label} ${brand.muted(`(${question.type})${options}`)}`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
console.log();
|
|
393
|
+
}
|
|
153
394
|
}
|
package/dist/src/help.js
CHANGED
|
@@ -60,6 +60,7 @@ export function renderRootHelp() {
|
|
|
60
60
|
lines.push(`${INDENT}${brand.muted('$')} feedbackbasket feedback list --category BUG --status OPEN`);
|
|
61
61
|
lines.push(`${INDENT}${brand.muted('$')} feedbackbasket bugs list --severity high`);
|
|
62
62
|
lines.push(`${INDENT}${brand.muted('$')} feedbackbasket widget script myapp`);
|
|
63
|
+
lines.push(`${INDENT}${brand.muted('$')} feedbackbasket widget flow myapp --reset-default --enable`);
|
|
63
64
|
lines.push(`${INDENT}${brand.muted('$')} feedbackbasket projects create "My App" --url https://myapp.com`);
|
|
64
65
|
lines.push('');
|
|
65
66
|
// Learn More
|
package/dist/src/types.d.ts
CHANGED
|
@@ -2,6 +2,47 @@ export type FeedbackStatus = 'OPEN' | 'UNDER_REVIEW' | 'PLANNED' | 'IN_PROGRESS'
|
|
|
2
2
|
export type FeedbackCategory = 'BUG' | 'FEATURE_REQUEST' | 'IMPROVEMENT' | 'QUESTION';
|
|
3
3
|
export type Sentiment = 'POSITIVE' | 'NEGATIVE' | 'NEUTRAL';
|
|
4
4
|
export type Severity = 'high' | 'medium' | 'low';
|
|
5
|
+
export type FeedbackFlowQuestionType = 'text' | 'textarea' | 'single_choice';
|
|
6
|
+
export interface FeedbackFlowQuestion {
|
|
7
|
+
id: string;
|
|
8
|
+
label: string;
|
|
9
|
+
type: FeedbackFlowQuestionType;
|
|
10
|
+
placeholder?: string;
|
|
11
|
+
options?: string[];
|
|
12
|
+
}
|
|
13
|
+
export interface FeedbackFlowType {
|
|
14
|
+
id: string;
|
|
15
|
+
emoji: string;
|
|
16
|
+
label: string;
|
|
17
|
+
description: string;
|
|
18
|
+
questions: FeedbackFlowQuestion[];
|
|
19
|
+
}
|
|
20
|
+
export interface FeedbackFlowSettings {
|
|
21
|
+
enabled: boolean;
|
|
22
|
+
mode: 'guided';
|
|
23
|
+
types: FeedbackFlowType[];
|
|
24
|
+
}
|
|
25
|
+
export interface WidgetSettings {
|
|
26
|
+
widgetType?: string;
|
|
27
|
+
triggerMode?: 'floating' | 'inline';
|
|
28
|
+
buttonColor?: string;
|
|
29
|
+
buttonRadius?: number;
|
|
30
|
+
buttonLabel?: string;
|
|
31
|
+
buttonSize?: 'mini' | 'regular';
|
|
32
|
+
iconOnly?: boolean;
|
|
33
|
+
showIcon?: boolean;
|
|
34
|
+
icon?: string;
|
|
35
|
+
introMessage?: string;
|
|
36
|
+
successMessage?: string;
|
|
37
|
+
position?: string;
|
|
38
|
+
showEmailField?: boolean;
|
|
39
|
+
emailRequired?: boolean;
|
|
40
|
+
allowAttachments?: boolean;
|
|
41
|
+
displayMode?: 'modal' | 'popup';
|
|
42
|
+
zIndex?: number;
|
|
43
|
+
showBranding?: boolean;
|
|
44
|
+
feedbackFlow?: FeedbackFlowSettings;
|
|
45
|
+
}
|
|
5
46
|
export interface Project {
|
|
6
47
|
id: string;
|
|
7
48
|
name: string;
|
|
@@ -23,6 +64,18 @@ export interface Feedback {
|
|
|
23
64
|
aiSummary?: string | null;
|
|
24
65
|
aiPriorityScore?: number | null;
|
|
25
66
|
reasoning?: string | null;
|
|
67
|
+
feedbackType?: {
|
|
68
|
+
id?: string;
|
|
69
|
+
emoji?: string;
|
|
70
|
+
label?: string;
|
|
71
|
+
description?: string;
|
|
72
|
+
} | null;
|
|
73
|
+
followUpAnswers?: Array<{
|
|
74
|
+
questionId: string;
|
|
75
|
+
label: string;
|
|
76
|
+
type: FeedbackFlowQuestionType;
|
|
77
|
+
value: string;
|
|
78
|
+
}> | null;
|
|
26
79
|
pageUrl?: string | null;
|
|
27
80
|
browser?: string | null;
|
|
28
81
|
os?: string | null;
|
package/dist/src/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.
|
|
2
|
-
export declare const USER_AGENT = "FeedbackBasket-CLI/0.
|
|
1
|
+
export declare const VERSION = "0.6.0";
|
|
2
|
+
export declare const USER_AGENT = "FeedbackBasket-CLI/0.6.0";
|
package/dist/src/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const VERSION = '0.
|
|
1
|
+
export const VERSION = '0.6.0';
|
|
2
2
|
export const USER_AGENT = `FeedbackBasket-CLI/${VERSION}`;
|
package/package.json
CHANGED
|
@@ -21,7 +21,7 @@ Full command-line interface for managing feedback, bug reports, projects, widget
|
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
23
|
feedbackbasket login # Opens browser — one click, full access
|
|
24
|
-
feedbackbasket login --manual #
|
|
24
|
+
feedbackbasket login --manual # No localhost browser callback (remote servers)
|
|
25
25
|
feedbackbasket login --token <TOKEN> # Manual token (CI/headless)
|
|
26
26
|
feedbackbasket auth status # Check auth state
|
|
27
27
|
feedbackbasket doctor # Full diagnostics
|
|
@@ -96,8 +96,17 @@ feedbackbasket widget settings <project>
|
|
|
96
96
|
feedbackbasket widget settings <project> --color "#22c55e" --label "Send Feedback"
|
|
97
97
|
feedbackbasket widget settings <project> --position bottom-left --display modal
|
|
98
98
|
feedbackbasket widget settings <project> --email-required --intro "How can we improve?"
|
|
99
|
+
feedbackbasket widget settings <project> --show-email --allow-attachments --guided
|
|
100
|
+
|
|
101
|
+
# Guided feedback types and follow-up questions
|
|
102
|
+
feedbackbasket widget flow <project>
|
|
103
|
+
feedbackbasket widget flow <project> --enable
|
|
104
|
+
feedbackbasket widget flow <project> --reset-default --enable
|
|
105
|
+
feedbackbasket widget flow <project> --config ./feedback-flow.json
|
|
99
106
|
```
|
|
100
107
|
|
|
108
|
+
`widget flow --config` accepts either a `feedbackFlow` object or a JSON object with a `feedbackFlow` key. Use it when an agent needs to customize visitor choices such as Bug report, Feature request, and General feedback. Supported v1 question types are `text`, `textarea`, and `single_choice`.
|
|
109
|
+
|
|
101
110
|
### Team
|
|
102
111
|
```bash
|
|
103
112
|
feedbackbasket team list
|
|
@@ -119,6 +128,8 @@ feedbackbasket projects create "My App" --url https://myapp.com --agent
|
|
|
119
128
|
feedbackbasket widget script "My App" --agent
|
|
120
129
|
# Agent gets the embed code, adds it to the HTML
|
|
121
130
|
feedbackbasket widget settings "My App" --color "#22c55e" --label "Feedback" --agent
|
|
131
|
+
# Optional: enable the guided wizard with Bug, Feature, and General templates
|
|
132
|
+
feedbackbasket widget flow "My App" --reset-default --enable --agent
|
|
122
133
|
```
|
|
123
134
|
|
|
124
135
|
### Triage new feedback
|
|
@@ -133,7 +144,7 @@ feedbackbasket feedback note <id> "Reviewing — appears related to auth flow" -
|
|
|
133
144
|
```bash
|
|
134
145
|
feedbackbasket bugs list --severity high --agent
|
|
135
146
|
feedbackbasket feedback show <id> --agent
|
|
136
|
-
# Response includes browser, OS, page URL, AI analysis, priority score
|
|
147
|
+
# Response includes browser, OS, page URL, submitted feedback type, follow-up answers, AI analysis, priority score
|
|
137
148
|
```
|
|
138
149
|
|
|
139
150
|
### Close the loop — reply to the submitter
|