feedbackbasket-cli 0.11.0 → 0.12.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 +336 -330
- package/dist/src/client.d.ts +1 -0
- package/dist/src/commands/feedback-reply.js +28 -19
- package/dist/src/commands/feedback.js +1 -0
- package/dist/src/commands/mobile.js +27 -0
- package/dist/src/commands/widget.js +6 -1
- package/dist/src/types.d.ts +4 -0
- package/dist/src/version.d.ts +2 -2
- package/dist/src/version.js +1 -1
- package/package.json +49 -49
- package/skills/feedbackbasket/SKILL.md +390 -385
package/dist/src/client.d.ts
CHANGED
|
@@ -56,6 +56,7 @@ export declare class FeedbackBasketClient {
|
|
|
56
56
|
getMobileIntegration(projectId: string, includePublishableKey?: boolean): Promise<MobileIntegrationResponse>;
|
|
57
57
|
updateMobileIntegration(projectId: string, data: {
|
|
58
58
|
enabled?: boolean;
|
|
59
|
+
allowVisitorReplies?: boolean;
|
|
59
60
|
addBundleIds?: string[];
|
|
60
61
|
removeBundleIds?: string[];
|
|
61
62
|
}, includePublishableKey?: boolean): Promise<MobileIntegrationResponse>;
|
|
@@ -96,44 +96,53 @@ export function createFeedbackReplyCommand(getWriter) {
|
|
|
96
96
|
export function createFeedbackRepliesCommand(getWriter) {
|
|
97
97
|
return new Command('replies')
|
|
98
98
|
.argument('<id>', 'Feedback ID')
|
|
99
|
-
.description('List
|
|
99
|
+
.description('List the complete feedback conversation')
|
|
100
100
|
.action(async (id) => {
|
|
101
101
|
const writer = getWriter();
|
|
102
102
|
const client = requireClient();
|
|
103
103
|
const result = await client.listReplies(id);
|
|
104
104
|
const messages = result.messages ?? [];
|
|
105
|
-
const
|
|
105
|
+
const linkedReplyIds = new Set(messages.map((item) => item.replyId).filter((replyId) => replyId !== null));
|
|
106
|
+
const visibleEmailReplies = result.replies.filter((item) => !linkedReplyIds.has(item.id));
|
|
107
|
+
const conversation = [
|
|
108
|
+
...visibleEmailReplies.map((item) => ({ kind: 'email', item })),
|
|
109
|
+
...messages.map((item) => ({ kind: 'thread', item })),
|
|
110
|
+
].sort((left, right) => new Date(left.item.createdAt).getTime() - new Date(right.item.createdAt).getTime());
|
|
106
111
|
if (!writer.isMachineOutput()) {
|
|
107
112
|
if (result.total === 0) {
|
|
108
113
|
console.log(brand.muted(' No replies sent yet.'));
|
|
109
114
|
console.log();
|
|
110
115
|
}
|
|
111
116
|
else {
|
|
112
|
-
console.log(brand.bold(`${result.total}
|
|
117
|
+
console.log(brand.bold(`${result.total} conversation message${result.total === 1 ? '' : 's'} for feedback ${id}`));
|
|
113
118
|
console.log();
|
|
114
|
-
for (const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
console.log(
|
|
119
|
+
for (const entry of conversation) {
|
|
120
|
+
if (entry.kind === 'email') {
|
|
121
|
+
const emailReply = entry.item;
|
|
122
|
+
console.log(` ${brand.success('->')} ${brand.bold(emailReply.sentBy)} ${brand.muted(emailReply.createdAt)}`);
|
|
123
|
+
console.log(` ${brand.muted('Delivery:')} email`);
|
|
124
|
+
console.log(` ${brand.muted('Reply-to:')} ${emailReply.replyToEmail}`);
|
|
125
|
+
console.log();
|
|
126
|
+
for (const line of emailReply.content.split('\n')) {
|
|
127
|
+
console.log(` ${line}`);
|
|
128
|
+
}
|
|
121
129
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
+
else {
|
|
131
|
+
const message = entry.item;
|
|
132
|
+
const visitor = message.senderType === 'VISITOR';
|
|
133
|
+
console.log(` ${visitor ? brand.primary('<-') : brand.success('->')} ${brand.bold(visitor ? 'User' : (message.sentByName ?? 'CLI'))} ${brand.muted(message.createdAt)}`);
|
|
134
|
+
console.log(` ${brand.muted('Delivery:')} ${visitor ? 'visitor follow-up' : message.delivery === 'BOTH' ? 'email + widget/in-app' : 'widget/in-app'}`);
|
|
135
|
+
console.log();
|
|
136
|
+
for (const line of message.content.split('\n')) {
|
|
137
|
+
console.log(` ${line}`);
|
|
138
|
+
}
|
|
130
139
|
}
|
|
131
140
|
console.log();
|
|
132
141
|
}
|
|
133
142
|
}
|
|
134
143
|
}
|
|
135
144
|
writer.ok({ replies: result.replies, messages }, {
|
|
136
|
-
summary: `${result.total}
|
|
145
|
+
summary: `${result.total} conversation message${result.total === 1 ? '' : 's'}`,
|
|
137
146
|
breadcrumbs: [
|
|
138
147
|
{ action: 'Send an email reply', cmd: `feedbackbasket feedback reply ${id} "<content>" --delivery email` },
|
|
139
148
|
{ action: 'Post a widget reply', cmd: `feedbackbasket feedback reply ${id} "<content>" --delivery widget` },
|
|
@@ -187,6 +187,7 @@ function renderFeedbackDetail(item) {
|
|
|
187
187
|
['Device', item.device],
|
|
188
188
|
['Language', item.language],
|
|
189
189
|
['Reply Channel', item.replyChannel === 'in_app' ? 'In-app' : item.hasWidgetAccess ? 'Widget' : null],
|
|
190
|
+
['Conversation', item.awaitingOwnerReply ? 'User replied — team response needed' : null],
|
|
190
191
|
['Created', item.createdAt],
|
|
191
192
|
];
|
|
192
193
|
for (const [label, value] of fields) {
|
|
@@ -31,6 +31,7 @@ export function createMobileCommand(getWriter) {
|
|
|
31
31
|
? [
|
|
32
32
|
{ action: 'Verify SDK connection', cmd: `feedbackbasket mobile verify ${ref}` },
|
|
33
33
|
{ action: 'Add a bundle ID', cmd: `feedbackbasket mobile bundle-ids ${ref} --add com.example.app` },
|
|
34
|
+
{ action: 'Configure conversations', cmd: `feedbackbasket mobile conversations ${ref} --enable` },
|
|
34
35
|
]
|
|
35
36
|
: [
|
|
36
37
|
{ action: 'Enable mobile feedback', cmd: `feedbackbasket mobile setup ${ref}` },
|
|
@@ -97,6 +98,31 @@ export function createMobileCommand(getWriter) {
|
|
|
97
98
|
],
|
|
98
99
|
});
|
|
99
100
|
});
|
|
101
|
+
mobile
|
|
102
|
+
.command('conversations [project]')
|
|
103
|
+
.description('Enable or disable in-app follow-up replies')
|
|
104
|
+
.option('--enable', 'Let users continue feedback conversations in the app')
|
|
105
|
+
.option('--disable', 'Keep in-app team replies read-only')
|
|
106
|
+
.action(async (projectArg, opts) => {
|
|
107
|
+
if (Boolean(opts.enable) === Boolean(opts.disable)) {
|
|
108
|
+
throw errUsage('Choose either --enable or --disable');
|
|
109
|
+
}
|
|
110
|
+
const writer = getWriter();
|
|
111
|
+
const client = requireClient();
|
|
112
|
+
const projectId = await resolveProjectId(client, projectArg);
|
|
113
|
+
const allowVisitorReplies = Boolean(opts.enable);
|
|
114
|
+
const result = await client.updateMobileIntegration(projectId, { allowVisitorReplies });
|
|
115
|
+
if (!writer.isMachineOutput()) {
|
|
116
|
+
console.log(` ${brand.success('✓')} In-app conversations ${allowVisitorReplies ? 'enabled' : 'disabled'} for ${brand.bold(result.project.name)}`);
|
|
117
|
+
console.log();
|
|
118
|
+
}
|
|
119
|
+
writer.ok(result, {
|
|
120
|
+
summary: `${allowVisitorReplies ? 'Enabled' : 'Disabled'} in-app follow-up replies for "${result.project.name}"`,
|
|
121
|
+
breadcrumbs: [
|
|
122
|
+
{ action: 'View mobile status', cmd: `feedbackbasket mobile status ${projectRef(projectArg, projectId)}` },
|
|
123
|
+
],
|
|
124
|
+
});
|
|
125
|
+
});
|
|
100
126
|
mobile
|
|
101
127
|
.command('verify [project]')
|
|
102
128
|
.description('Verify that the SDK has connected from the expected app')
|
|
@@ -269,6 +295,7 @@ function renderMobileStatus(result) {
|
|
|
269
295
|
}
|
|
270
296
|
const integration = result.integration;
|
|
271
297
|
console.log(` ${brand.label('Status'.padEnd(18))} ${integration.enabled ? 'Enabled' : 'Disabled'}`);
|
|
298
|
+
console.log(` ${brand.label('Conversations'.padEnd(18))} ${integration.allowVisitorReplies ? 'Enabled' : 'Read-only'}`);
|
|
272
299
|
console.log(` ${brand.label('Publishable key'.padEnd(18))} ${integration.publishableKey}`);
|
|
273
300
|
console.log(` ${brand.label('Bundle IDs'.padEnd(18))} ${integration.bundleIds.join(', ') || 'Any bundle ID'}`);
|
|
274
301
|
console.log(` ${brand.label('Connection'.padEnd(18))} ${integration.connection.connected ? 'Connected' : 'Waiting for first heartbeat'}`);
|
|
@@ -72,6 +72,8 @@ export function createWidgetCommand(getWriter) {
|
|
|
72
72
|
.option('--no-hide-email-when-prefilled', 'Show the email field even when userEmail is prefilled')
|
|
73
73
|
.option('--allow-attachments', 'Allow image attachments')
|
|
74
74
|
.option('--no-allow-attachments', 'Disable image attachments')
|
|
75
|
+
.option('--allow-visitor-replies', 'Let visitors continue feedback conversations')
|
|
76
|
+
.option('--no-allow-visitor-replies', 'Keep widget replies read-only for visitors')
|
|
75
77
|
.option('--icon-only', 'Show only the icon, no label')
|
|
76
78
|
.option('--no-icon-only', 'Show both icon and label')
|
|
77
79
|
.option('--show-icon', 'Show an icon next to the label')
|
|
@@ -94,7 +96,7 @@ export function createWidgetCommand(getWriter) {
|
|
|
94
96
|
opts.buttonRadius || opts.buttonSize || opts.icon ||
|
|
95
97
|
opts.emailRequired !== undefined || opts.showEmail !== undefined ||
|
|
96
98
|
opts.emailReadOnly !== undefined || opts.hideEmailWhenPrefilled !== undefined ||
|
|
97
|
-
opts.allowAttachments !== undefined || opts.iconOnly !== undefined ||
|
|
99
|
+
opts.allowAttachments !== undefined || opts.allowVisitorReplies !== undefined || opts.iconOnly !== undefined ||
|
|
98
100
|
opts.showIcon !== undefined || opts.showBranding !== undefined ||
|
|
99
101
|
opts.allowConsoleErrors !== undefined || opts.errorTracking !== undefined ||
|
|
100
102
|
opts.zIndex || opts.guided || opts.disableGuided;
|
|
@@ -139,6 +141,8 @@ export function createWidgetCommand(getWriter) {
|
|
|
139
141
|
settings.hideEmailFieldWhenPrefilled = opts.hideEmailWhenPrefilled;
|
|
140
142
|
if (opts.allowAttachments !== undefined)
|
|
141
143
|
settings.allowAttachments = opts.allowAttachments;
|
|
144
|
+
if (opts.allowVisitorReplies !== undefined)
|
|
145
|
+
settings.allowVisitorReplies = opts.allowVisitorReplies;
|
|
142
146
|
if (opts.iconOnly !== undefined)
|
|
143
147
|
settings.iconOnly = opts.iconOnly;
|
|
144
148
|
if (opts.showIcon !== undefined)
|
|
@@ -434,6 +438,7 @@ function renderWidgetSettings(projectName, settings) {
|
|
|
434
438
|
['Email Read Only', String(settings.emailReadOnly ?? false)],
|
|
435
439
|
['Hide Prefilled Email', String(settings.hideEmailFieldWhenPrefilled ?? false)],
|
|
436
440
|
['Attachments', String(settings.allowAttachments ?? true)],
|
|
441
|
+
['Follow-up Replies', String(settings.allowVisitorReplies ?? false)],
|
|
437
442
|
['Guided Flow', String(settings.feedbackFlow?.enabled ?? false)],
|
|
438
443
|
['Intro Message', String(settings.introMessage ?? '')],
|
|
439
444
|
['Success Message', String(settings.successMessage ?? '')],
|
package/dist/src/types.d.ts
CHANGED
|
@@ -42,6 +42,7 @@ export interface WidgetSettings {
|
|
|
42
42
|
emailReadOnly?: boolean;
|
|
43
43
|
hideEmailFieldWhenPrefilled?: boolean;
|
|
44
44
|
allowAttachments?: boolean;
|
|
45
|
+
allowVisitorReplies?: boolean;
|
|
45
46
|
displayMode?: 'modal' | 'popup';
|
|
46
47
|
zIndex?: number;
|
|
47
48
|
showBranding?: boolean;
|
|
@@ -68,6 +69,7 @@ export interface MobileIntegrationResponse {
|
|
|
68
69
|
};
|
|
69
70
|
integration: {
|
|
70
71
|
enabled: boolean;
|
|
72
|
+
allowVisitorReplies: boolean;
|
|
71
73
|
publishableKey: string;
|
|
72
74
|
publishableKeyIncluded: boolean;
|
|
73
75
|
hostedFormUrl: string | null;
|
|
@@ -95,6 +97,7 @@ export interface Feedback {
|
|
|
95
97
|
sentiment?: Sentiment | null;
|
|
96
98
|
aiSummary?: string | null;
|
|
97
99
|
aiPriorityScore?: number | null;
|
|
100
|
+
awaitingOwnerReply?: boolean;
|
|
98
101
|
reasoning?: string | null;
|
|
99
102
|
feedbackType?: {
|
|
100
103
|
id?: string;
|
|
@@ -202,6 +205,7 @@ export interface FeedbackReplyResponse {
|
|
|
202
205
|
} | null;
|
|
203
206
|
message: {
|
|
204
207
|
id: string;
|
|
208
|
+
senderType: 'OWNER' | 'VISITOR';
|
|
205
209
|
content: string;
|
|
206
210
|
delivery: 'WIDGET' | 'EMAIL' | 'BOTH';
|
|
207
211
|
sentByName: 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.12.0";
|
|
2
|
+
export declare const USER_AGENT = "FeedbackBasket-CLI/0.12.0";
|
package/dist/src/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const VERSION = '0.
|
|
1
|
+
export const VERSION = '0.12.0';
|
|
2
2
|
export const USER_AGENT = `FeedbackBasket-CLI/${VERSION}`;
|
package/package.json
CHANGED
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "feedbackbasket-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Command-line interface for FeedbackBasket — manage feedback and waitlists from your terminal",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "dist/src/cli.js",
|
|
7
|
-
"bin": {
|
|
8
|
-
"feedbackbasket": "dist/bin/feedbackbasket.js"
|
|
9
|
-
},
|
|
10
|
-
"scripts": {
|
|
11
|
-
"build": "tsc",
|
|
12
|
-
"test": "tsx --test tests/*.test.ts",
|
|
13
|
-
"dev": "tsx bin/feedbackbasket.ts",
|
|
14
|
-
"start": "node dist/bin/feedbackbasket.js",
|
|
15
|
-
"prepublishOnly": "npm run build"
|
|
16
|
-
},
|
|
17
|
-
"keywords": [
|
|
18
|
-
"feedbackbasket",
|
|
19
|
-
"feedback",
|
|
20
|
-
"cli",
|
|
21
|
-
"ai-agent",
|
|
22
|
-
"developer-tools"
|
|
23
|
-
],
|
|
24
|
-
"author": "deifosv",
|
|
25
|
-
"license": "MIT",
|
|
26
|
-
"repository": {
|
|
27
|
-
"type": "git",
|
|
28
|
-
"url": "https://github.com/deifos/feedbackbasket-cli.git"
|
|
29
|
-
},
|
|
30
|
-
"homepage": "https://feedbackbasket.com",
|
|
31
|
-
"dependencies": {
|
|
32
|
-
"chalk": "^5.3.0",
|
|
33
|
-
"commander": "^13.1.0",
|
|
34
|
-
"open": "^10.1.0"
|
|
35
|
-
},
|
|
36
|
-
"devDependencies": {
|
|
37
|
-
"@types/node": "^22.0.0",
|
|
38
|
-
"tsx": "^4.23.0",
|
|
39
|
-
"typescript": "^5.7.0"
|
|
40
|
-
},
|
|
41
|
-
"engines": {
|
|
42
|
-
"node": ">=18"
|
|
43
|
-
},
|
|
44
|
-
"files": [
|
|
45
|
-
"dist/**/*",
|
|
46
|
-
"skills/**/*",
|
|
47
|
-
"README.md"
|
|
48
|
-
]
|
|
49
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "feedbackbasket-cli",
|
|
3
|
+
"version": "0.12.0",
|
|
4
|
+
"description": "Command-line interface for FeedbackBasket — manage feedback and waitlists from your terminal",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/src/cli.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"feedbackbasket": "dist/bin/feedbackbasket.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"test": "tsx --test tests/*.test.ts",
|
|
13
|
+
"dev": "tsx bin/feedbackbasket.ts",
|
|
14
|
+
"start": "node dist/bin/feedbackbasket.js",
|
|
15
|
+
"prepublishOnly": "npm run build"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"feedbackbasket",
|
|
19
|
+
"feedback",
|
|
20
|
+
"cli",
|
|
21
|
+
"ai-agent",
|
|
22
|
+
"developer-tools"
|
|
23
|
+
],
|
|
24
|
+
"author": "deifosv",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/deifos/feedbackbasket-cli.git"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://feedbackbasket.com",
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"chalk": "^5.3.0",
|
|
33
|
+
"commander": "^13.1.0",
|
|
34
|
+
"open": "^10.1.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^22.0.0",
|
|
38
|
+
"tsx": "^4.23.0",
|
|
39
|
+
"typescript": "^5.7.0"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"dist/**/*",
|
|
46
|
+
"skills/**/*",
|
|
47
|
+
"README.md"
|
|
48
|
+
]
|
|
49
|
+
}
|