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.
@@ -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 all replies sent for a feedback item')
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 visibleWidgetMessages = messages.filter((item) => !item.replyId);
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} repl${result.total === 1 ? 'y' : 'ies'} for feedback ${id}`));
117
+ console.log(brand.bold(`${result.total} conversation message${result.total === 1 ? '' : 's'} for feedback ${id}`));
113
118
  console.log();
114
- for (const r of result.replies) {
115
- console.log(` ${brand.success('->')} ${brand.bold(r.sentBy)} ${brand.muted(r.createdAt)}`);
116
- console.log(` ${brand.muted('Delivery:')} email`);
117
- console.log(` ${brand.muted('Reply-to:')} ${r.replyToEmail}`);
118
- console.log();
119
- for (const line of r.content.split('\n')) {
120
- console.log(` ${line}`);
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
- console.log();
123
- }
124
- for (const message of visibleWidgetMessages) {
125
- console.log(` ${brand.success('->')} ${brand.bold(message.sentByName ?? 'CLI')} ${brand.muted(message.createdAt)}`);
126
- console.log(` ${brand.muted('Delivery:')} widget`);
127
- console.log();
128
- for (const line of message.content.split('\n')) {
129
- console.log(` ${line}`);
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} repl${result.total === 1 ? 'y' : 'ies'}`,
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 ?? '')],
@@ -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;
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.11.0";
2
- export declare const USER_AGENT = "FeedbackBasket-CLI/0.11.0";
1
+ export declare const VERSION = "0.12.0";
2
+ export declare const USER_AGENT = "FeedbackBasket-CLI/0.12.0";
@@ -1,2 +1,2 @@
1
- export const VERSION = '0.11.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.11.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
+ }