feedbackbasket-cli 0.6.1 → 0.8.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 CHANGED
@@ -22,6 +22,7 @@ feedbackbasket login --manual
22
22
  # Start exploring
23
23
  feedbackbasket projects list
24
24
  feedbackbasket feedback list
25
+ feedbackbasket feedback create "Login button is broken" --project myapp --type bug
25
26
  feedbackbasket bugs list --severity high
26
27
  ```
27
28
 
@@ -35,6 +36,7 @@ Any AI agent with shell access (Claude Code, Codex, Cursor, OpenCode) can use th
35
36
  # Agents should use --agent flag for raw JSON output
36
37
  feedbackbasket projects list --agent
37
38
  feedbackbasket feedback list --category BUG --agent
39
+ feedbackbasket feedback create "Login button is broken" --content "Clicking Log in does nothing in Safari." --project myapp --type bug --agent
38
40
  feedbackbasket feedback update <id> --status PLANNED --agent
39
41
  feedbackbasket widget script myproject --agent
40
42
  ```
@@ -87,6 +89,8 @@ feedbackbasket feedback show <id> # View detail, includ
87
89
  feedbackbasket feedback search "crash on mobile" # Search shortcut
88
90
 
89
91
  # Write
92
+ feedbackbasket feedback create "Title" --content "Body" --project myapp
93
+ feedbackbasket feedback create "Login bug" --content "Clicking Log in does nothing" --project myapp --type bug --page-url https://example.com/login
90
94
  feedbackbasket feedback update <id> --status PLANNED # Update status
91
95
  feedbackbasket feedback update <id> --category BUG # Update category
92
96
  feedbackbasket feedback reply <id> "Thanks for reporting!" # Email the submitter
@@ -123,6 +127,7 @@ feedbackbasket widget settings myapp --position bottom-left --display modal
123
127
  feedbackbasket widget settings myapp --email-required --intro "How can we improve?"
124
128
  feedbackbasket widget settings myapp --button-radius 10 --button-size regular
125
129
  feedbackbasket widget settings myapp --show-email --allow-attachments --guided
130
+ feedbackbasket widget settings myapp --email-read-only --hide-email-when-prefilled
126
131
 
127
132
  # Configure guided feedback types and follow-up questions
128
133
  feedbackbasket widget flow myapp
@@ -134,6 +139,8 @@ feedbackbasket widget flow myapp --config ./feedback-flow.json
134
139
  feedbackbasket widget script myapp
135
140
  ```
136
141
 
142
+ Use `--email-read-only` and `--hide-email-when-prefilled` with runtime `userEmail` values from your app. These settings do not store visitor emails in FeedbackBasket widget settings.
143
+
137
144
  `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
145
 
139
146
  ```json
@@ -1,4 +1,4 @@
1
- import type { ProjectsResponse, FeedbackResponse, BugReportsResponse, FeedbackParams, BugReportParams, UserProfile, Project, Feedback, WidgetSettings } from './types.js';
1
+ import type { ProjectsResponse, FeedbackResponse, BugReportsResponse, FeedbackParams, FeedbackCreateInput, FeedbackCreateResponse, 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);
@@ -34,6 +34,7 @@ export declare class FeedbackBasketClient {
34
34
  category?: string;
35
35
  limit?: number;
36
36
  }): Promise<FeedbackResponse>;
37
+ createFeedback(data: FeedbackCreateInput): Promise<FeedbackCreateResponse>;
37
38
  getWidgetSettings(projectId: string): Promise<{
38
39
  projectId: string;
39
40
  projectName: string;
@@ -50,6 +50,9 @@ export class FeedbackBasketClient {
50
50
  async searchFeedback(query, opts = {}) {
51
51
  return this.getFeedback({ search: query, ...opts });
52
52
  }
53
+ async createFeedback(data) {
54
+ return this.request('POST', '/feedback', data);
55
+ }
53
56
  // Widget
54
57
  async getWidgetSettings(projectId) {
55
58
  return this.request('GET', `/projects/${encodeURIComponent(projectId)}/widget`);
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ import type { OutputWriter } from '../output/writer.js';
3
+ export declare function createFeedbackCreateCommand(getWriter: () => OutputWriter): Command;
@@ -0,0 +1,120 @@
1
+ import { Command } from 'commander';
2
+ import { FeedbackBasketClient } from '../client.js';
3
+ import { AuthManager } from '../auth/manager.js';
4
+ import { loadConfig } from '../config/config.js';
5
+ import { errAuth, errUsage } from '../output/errors.js';
6
+ import { brand } from '../output/theme.js';
7
+ import { resolveProject } from '../resolve.js';
8
+ const validTypes = new Set(['bug', 'feature', 'general']);
9
+ const validCategories = new Set(['BUG', 'FEATURE_REQUEST', 'IMPROVEMENT', 'QUESTION']);
10
+ const validStatuses = new Set(['OPEN', 'UNDER_REVIEW', 'PLANNED', 'IN_PROGRESS', 'COMPLETE', 'CLOSED']);
11
+ export function createFeedbackCreateCommand(getWriter) {
12
+ return new Command('create')
13
+ .argument('<title>', 'Short title or summary for the feedback')
14
+ .description('Create a new feedback item')
15
+ .requiredOption('--project <id-or-name>', 'Project ID or name')
16
+ .option('--content <body>', 'Longer feedback body')
17
+ .option('--type <type>', 'Feedback type (bug, feature, general)')
18
+ .option('--category <category>', 'Category (BUG, FEATURE_REQUEST, IMPROVEMENT, QUESTION)')
19
+ .option('--status <status>', 'Initial status (OPEN, UNDER_REVIEW, PLANNED, IN_PROGRESS, COMPLETE, CLOSED)')
20
+ .option('--email <email>', 'Submitter email')
21
+ .option('--page-url <url>', 'Page URL where the feedback applies')
22
+ .option('--metadata <key=value>', 'Metadata key/value pair (repeatable)', collectMetadata, [])
23
+ .action(async (title, opts) => {
24
+ const writer = getWriter();
25
+ const client = requireClient();
26
+ if (opts.type && !validTypes.has(opts.type)) {
27
+ throw errUsage('Invalid type. Must be one of: bug, feature, general', 'Example: feedbackbasket feedback create "Login is broken" --project myapp --type bug');
28
+ }
29
+ if (opts.category && !validCategories.has(opts.category)) {
30
+ throw errUsage('Invalid category. Must be one of: BUG, FEATURE_REQUEST, IMPROVEMENT, QUESTION');
31
+ }
32
+ if (opts.status && !validStatuses.has(opts.status)) {
33
+ throw errUsage('Invalid status. Must be one of: OPEN, UNDER_REVIEW, PLANNED, IN_PROGRESS, COMPLETE, CLOSED');
34
+ }
35
+ const project = await resolveProject(client, opts.project);
36
+ const content = composeContent(title, opts.content);
37
+ const metadata = parseMetadata(opts.metadata ?? []);
38
+ const result = await client.createFeedback({
39
+ projectId: project.id,
40
+ content,
41
+ type: opts.type,
42
+ category: opts.category,
43
+ status: opts.status,
44
+ email: opts.email,
45
+ pageUrl: opts.pageUrl,
46
+ metadata,
47
+ });
48
+ if (!writer.isMachineOutput()) {
49
+ console.log(` ${brand.success('[OK]')} Feedback created: ${brand.bold(result.id)}`);
50
+ console.log(` ${brand.muted('Project:')} ${project.name}`);
51
+ console.log(` ${brand.muted('URL:')} ${result.url}`);
52
+ console.log();
53
+ }
54
+ writer.ok(result, {
55
+ summary: `Created feedback ${result.id}`,
56
+ breadcrumbs: [
57
+ { action: 'View feedback', cmd: `feedbackbasket feedback show ${result.id}` },
58
+ { action: 'Update status', cmd: `feedbackbasket feedback update ${result.id} --status UNDER_REVIEW` },
59
+ { action: 'List project feedback', cmd: `feedbackbasket feedback list --project ${project.id}` },
60
+ ],
61
+ });
62
+ });
63
+ }
64
+ function requireClient() {
65
+ const manager = new AuthManager();
66
+ const token = manager.resolveToken();
67
+ if (!token)
68
+ throw errAuth();
69
+ const config = loadConfig();
70
+ return new FeedbackBasketClient(token, config.baseUrl);
71
+ }
72
+ function composeContent(title, body) {
73
+ const cleanTitle = title.trim();
74
+ const cleanBody = body?.trim();
75
+ const content = cleanBody ? `${cleanTitle}\n\n${cleanBody}` : cleanTitle;
76
+ if (!content) {
77
+ throw errUsage('Feedback title is required');
78
+ }
79
+ if (content.length > 2000) {
80
+ throw errUsage('Feedback content must be 2000 characters or less');
81
+ }
82
+ return content;
83
+ }
84
+ function collectMetadata(value, previous) {
85
+ return previous.concat(value);
86
+ }
87
+ function parseMetadata(entries) {
88
+ if (entries.length === 0)
89
+ return undefined;
90
+ const metadata = {};
91
+ for (const entry of entries) {
92
+ const separator = entry.indexOf('=');
93
+ if (separator <= 0) {
94
+ throw errUsage(`Invalid metadata "${entry}"`, 'Use --metadata key=value, for example --metadata source=codex');
95
+ }
96
+ const key = entry.slice(0, separator).trim();
97
+ const rawValue = entry.slice(separator + 1).trim();
98
+ if (!key) {
99
+ throw errUsage('Metadata keys cannot be empty');
100
+ }
101
+ metadata[key] = parseMetadataValue(rawValue);
102
+ }
103
+ return metadata;
104
+ }
105
+ function parseMetadataValue(value) {
106
+ if (value === 'true')
107
+ return true;
108
+ if (value === 'false')
109
+ return false;
110
+ if (value === 'null')
111
+ return null;
112
+ if (value !== '' && Number.isFinite(Number(value)))
113
+ return Number(value);
114
+ try {
115
+ return JSON.parse(value);
116
+ }
117
+ catch {
118
+ return value;
119
+ }
120
+ }
@@ -17,6 +17,7 @@ async function resolveProjectId(client, optProject, all) {
17
17
  }
18
18
  import { createFeedbackUpdateCommand } from './feedback-update.js';
19
19
  import { createFeedbackNoteCommand } from './feedback-note.js';
20
+ import { createFeedbackCreateCommand } from './feedback-create.js';
20
21
  import { createFeedbackDeleteCommand } from './feedback-delete.js';
21
22
  import { createFeedbackBulkUpdateCommand } from './feedback-bulk-update.js';
22
23
  import { createFeedbackExportCommand } from './feedback-export.js';
@@ -25,6 +26,7 @@ export function createFeedbackCommand(getWriter) {
25
26
  const feedback = new Command('feedback')
26
27
  .description('View and manage feedback');
27
28
  // Write subcommands
29
+ feedback.addCommand(createFeedbackCreateCommand(getWriter));
28
30
  feedback.addCommand(createFeedbackUpdateCommand(getWriter));
29
31
  feedback.addCommand(createFeedbackNoteCommand(getWriter));
30
32
  feedback.addCommand(createFeedbackReplyCommand(getWriter));
@@ -213,6 +215,11 @@ function renderFeedbackDetail(item) {
213
215
  console.log(` ${attachment.url}`);
214
216
  }
215
217
  }
218
+ if (item.metadata && Object.keys(item.metadata).length > 0) {
219
+ console.log();
220
+ console.log(brand.bold('Metadata:'));
221
+ console.log(indentBlock(JSON.stringify(item.metadata, null, 2), ' '));
222
+ }
216
223
  if (item.aiSummary) {
217
224
  console.log();
218
225
  console.log(brand.bold('AI Summary:'));
@@ -240,6 +247,12 @@ function formatFeedbackType(item) {
240
247
  return undefined;
241
248
  return item.feedbackType.emoji ? `${item.feedbackType.emoji} ${label}` : label;
242
249
  }
250
+ function indentBlock(value, indent) {
251
+ return value
252
+ .split('\n')
253
+ .map((line) => `${indent}${line}`)
254
+ .join('\n');
255
+ }
243
256
  function formatBytes(bytes) {
244
257
  if (!Number.isFinite(bytes) || bytes < 0)
245
258
  return '';
@@ -65,6 +65,10 @@ export function createWidgetCommand(getWriter) {
65
65
  .option('--no-email-required', 'Make email optional')
66
66
  .option('--show-email', 'Show the email field')
67
67
  .option('--no-show-email', 'Hide the email field')
68
+ .option('--email-read-only', 'Lock the email field when userEmail is prefilled')
69
+ .option('--no-email-read-only', 'Allow editing prefilled email')
70
+ .option('--hide-email-when-prefilled', 'Hide the email field when userEmail is prefilled')
71
+ .option('--no-hide-email-when-prefilled', 'Show the email field even when userEmail is prefilled')
68
72
  .option('--allow-attachments', 'Allow image attachments')
69
73
  .option('--no-allow-attachments', 'Disable image attachments')
70
74
  .option('--icon-only', 'Show only the icon, no label')
@@ -84,6 +88,7 @@ export function createWidgetCommand(getWriter) {
84
88
  opts.success || opts.trigger || opts.display ||
85
89
  opts.buttonRadius || opts.buttonSize || opts.icon ||
86
90
  opts.emailRequired !== undefined || opts.showEmail !== undefined ||
91
+ opts.emailReadOnly !== undefined || opts.hideEmailWhenPrefilled !== undefined ||
87
92
  opts.allowAttachments !== undefined || opts.iconOnly !== undefined ||
88
93
  opts.showIcon !== undefined || opts.showBranding !== undefined ||
89
94
  opts.zIndex || opts.guided || opts.disableGuided;
@@ -117,6 +122,10 @@ export function createWidgetCommand(getWriter) {
117
122
  settings.emailRequired = opts.emailRequired;
118
123
  if (opts.showEmail !== undefined)
119
124
  settings.showEmailField = opts.showEmail;
125
+ if (opts.emailReadOnly !== undefined)
126
+ settings.emailReadOnly = opts.emailReadOnly;
127
+ if (opts.hideEmailWhenPrefilled !== undefined)
128
+ settings.hideEmailFieldWhenPrefilled = opts.hideEmailWhenPrefilled;
120
129
  if (opts.allowAttachments !== undefined)
121
130
  settings.allowAttachments = opts.allowAttachments;
122
131
  if (opts.iconOnly !== undefined)
@@ -349,6 +358,8 @@ function renderWidgetSettings(projectName, settings) {
349
358
  ['Display Mode', String(settings.displayMode ?? '')],
350
359
  ['Show Email', String(settings.showEmailField ?? true)],
351
360
  ['Email Required', String(settings.emailRequired ?? false)],
361
+ ['Email Read Only', String(settings.emailReadOnly ?? false)],
362
+ ['Hide Prefilled Email', String(settings.hideEmailFieldWhenPrefilled ?? false)],
352
363
  ['Attachments', String(settings.allowAttachments ?? true)],
353
364
  ['Guided Flow', String(settings.feedbackFlow?.enabled ?? false)],
354
365
  ['Intro Message', String(settings.introMessage ?? '')],
package/dist/src/help.js CHANGED
@@ -57,6 +57,7 @@ export function renderRootHelp() {
57
57
  // Examples
58
58
  lines.push(section(' EXAMPLES'));
59
59
  lines.push(`${INDENT}${brand.muted('$')} feedbackbasket projects list`);
60
+ lines.push(`${INDENT}${brand.muted('$')} feedbackbasket feedback create "Login bug" --project myapp --type bug`);
60
61
  lines.push(`${INDENT}${brand.muted('$')} feedbackbasket feedback list --category BUG --status OPEN`);
61
62
  lines.push(`${INDENT}${brand.muted('$')} feedbackbasket bugs list --severity high`);
62
63
  lines.push(`${INDENT}${brand.muted('$')} feedbackbasket widget script myapp`);
@@ -37,6 +37,8 @@ export interface WidgetSettings {
37
37
  position?: string;
38
38
  showEmailField?: boolean;
39
39
  emailRequired?: boolean;
40
+ emailReadOnly?: boolean;
41
+ hideEmailFieldWhenPrefilled?: boolean;
40
42
  allowAttachments?: boolean;
41
43
  displayMode?: 'modal' | 'popup';
42
44
  zIndex?: number;
@@ -76,6 +78,7 @@ export interface Feedback {
76
78
  type: FeedbackFlowQuestionType;
77
79
  value: string;
78
80
  }> | null;
81
+ metadata?: Record<string, unknown> | null;
79
82
  pageUrl?: string | null;
80
83
  browser?: string | null;
81
84
  os?: string | null;
@@ -123,6 +126,21 @@ export interface FeedbackResponse {
123
126
  feedback: Feedback[];
124
127
  pagination: Pagination;
125
128
  }
129
+ export interface FeedbackCreateInput {
130
+ projectId: string;
131
+ content: string;
132
+ type?: 'bug' | 'feature' | 'general';
133
+ category?: FeedbackCategory;
134
+ status?: FeedbackStatus;
135
+ email?: string;
136
+ pageUrl?: string;
137
+ metadata?: Record<string, unknown>;
138
+ }
139
+ export interface FeedbackCreateResponse {
140
+ id: string;
141
+ url: string;
142
+ feedback: Feedback;
143
+ }
126
144
  export interface BugReportsResponse {
127
145
  bugReports: BugReport[];
128
146
  stats: {
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.6.1";
2
- export declare const USER_AGENT = "FeedbackBasket-CLI/0.6.1";
1
+ export declare const VERSION = "0.8.0";
2
+ export declare const USER_AGENT = "FeedbackBasket-CLI/0.8.0";
@@ -1,2 +1,2 @@
1
- export const VERSION = '0.6.1';
1
+ export const VERSION = '0.8.0';
2
2
  export const USER_AGENT = `FeedbackBasket-CLI/${VERSION}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "feedbackbasket-cli",
3
- "version": "0.6.1",
3
+ "version": "0.8.0",
4
4
  "description": "Command-line interface for FeedbackBasket — manage feedback from your terminal",
5
5
  "type": "module",
6
6
  "main": "dist/src/cli.js",
@@ -62,6 +62,8 @@ feedbackbasket feedback show <id>
62
62
  feedbackbasket feedback search "crash on mobile" --project <id> --limit 10
63
63
 
64
64
  # Write
65
+ feedbackbasket feedback create "Login button is broken" --content "Clicking Log in does nothing in Safari." --project <id> --type bug
66
+ feedbackbasket feedback create "Feature idea" --content "Let users export saved views." --project <id> --type feature --metadata source=agent
65
67
  feedbackbasket feedback update <id> --status PLANNED --category BUG --sentiment NEGATIVE
66
68
  feedbackbasket feedback note <id> "Investigating — appears related to auth flow"
67
69
  feedbackbasket feedback delete <id> --yes
@@ -97,6 +99,7 @@ feedbackbasket widget settings <project> --color "#22c55e" --label "Send Feedbac
97
99
  feedbackbasket widget settings <project> --position bottom-left --display modal
98
100
  feedbackbasket widget settings <project> --email-required --intro "How can we improve?"
99
101
  feedbackbasket widget settings <project> --show-email --allow-attachments --guided
102
+ feedbackbasket widget settings <project> --email-read-only --hide-email-when-prefilled
100
103
 
101
104
  # Guided feedback types and follow-up questions
102
105
  feedbackbasket widget flow <project>
@@ -105,6 +108,8 @@ feedbackbasket widget flow <project> --reset-default --enable
105
108
  feedbackbasket widget flow <project> --config ./feedback-flow.json
106
109
  ```
107
110
 
111
+ `email-read-only` and `hide-email-when-prefilled` control behavior only when the host app passes a runtime `userEmail` value. Do not store visitor emails in widget settings.
112
+
108
113
  `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
114
 
110
115
  ### Team
@@ -140,11 +145,23 @@ feedbackbasket feedback update <id> --status UNDER_REVIEW --agent
140
145
  feedbackbasket feedback note <id> "Reviewing — appears related to auth flow" --agent
141
146
  ```
142
147
 
148
+ ### Capture new feedback without leaving the terminal
149
+ ```bash
150
+ feedbackbasket feedback create "Login button is broken" \
151
+ --content "Clicking Log in does nothing in Safari." \
152
+ --project myapp \
153
+ --type bug \
154
+ --page-url https://example.com/login \
155
+ --metadata source=agent \
156
+ --agent
157
+ ```
158
+ Agent mode returns the created feedback ID, dashboard URL, and feedback object. Created feedback is analyzed by AI and follows the project's notification settings.
159
+
143
160
  ### Investigate high-priority bugs
144
161
  ```bash
145
162
  feedbackbasket bugs list --severity high --agent
146
163
  feedbackbasket feedback show <id> --agent
147
- # Response includes browser, OS, page URL, submitted feedback type, follow-up answers, attachment URLs, AI analysis, priority score
164
+ # Response includes browser, OS, page URL, submitted feedback type, follow-up answers, attachment URLs, metadata, AI analysis, priority score
148
165
  ```
149
166
 
150
167
  ### Close the loop — reply to the submitter