feedbackbasket-cli 0.3.6 → 0.5.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
@@ -16,6 +16,9 @@ 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 flow: use when localhost browser callbacks cannot reach the CLI
20
+ feedbackbasket login --manual
21
+
19
22
  # Start exploring
20
23
  feedbackbasket projects list
21
24
  feedbackbasket feedback list
@@ -48,12 +51,15 @@ feedbackbasket setup claude
48
51
 
49
52
  ```bash
50
53
  feedbackbasket login # Browser OAuth flow (alias for auth login)
51
- feedbackbasket login --token <token> # Manual token (CI/headless)
54
+ feedbackbasket login --manual # No localhost browser callback (remote servers)
55
+ feedbackbasket login --token <token> # Use an existing CLI token (CI / scripts)
52
56
  feedbackbasket logout # Clear credentials (alias for auth logout)
53
57
  feedbackbasket auth status # Show auth state, scope, default project
54
58
  feedbackbasket auth token # Print raw token (for scripting/piping)
55
59
  ```
56
60
 
61
+ CLI tokens start with `fb_cli_`. MCP API keys start with `fb_key_` and are only for MCP server configuration.
62
+
57
63
  ### Projects
58
64
 
59
65
  All project commands accept **name or ID** (e.g. `feedbackbasket` or `cmn3c7sgv...`).
@@ -63,6 +69,7 @@ feedbackbasket projects list # List all projects wi
63
69
  feedbackbasket projects show <name-or-id> # Project details
64
70
  feedbackbasket projects create "My App" --url https://... # Create project
65
71
  feedbackbasket projects update myapp --name "New Name" # Update project
72
+ feedbackbasket projects update myapp --reply-to vlad@example.com # Set default reply-to email
66
73
  feedbackbasket projects delete myapp # Delete (with confirmation)
67
74
  ```
68
75
 
@@ -82,6 +89,8 @@ feedbackbasket feedback search "crash on mobile" # Search shortcut
82
89
  # Write
83
90
  feedbackbasket feedback update <id> --status PLANNED # Update status
84
91
  feedbackbasket feedback update <id> --category BUG # Update category
92
+ feedbackbasket feedback reply <id> "Thanks for reporting!" # Email the submitter
93
+ feedbackbasket feedback replies <id> # List sent replies
85
94
  feedbackbasket feedback note <id> "Investigating this..." # Add internal note
86
95
  feedbackbasket feedback delete <id> # Delete feedback
87
96
  feedbackbasket feedback bulk-update --status CLOSED --ids id1,id2,id3
@@ -2,5 +2,7 @@ interface LoginResult {
2
2
  token: string;
3
3
  scope: 'read' | 'full';
4
4
  }
5
+ export declare function isCliTokenFormat(token: string): boolean;
5
6
  export declare function browserLogin(baseUrl: string, scope?: 'read' | 'full'): Promise<LoginResult>;
7
+ export declare function manualLogin(baseUrl: string, scope?: 'read' | 'full'): Promise<LoginResult>;
6
8
  export {};
@@ -1,9 +1,15 @@
1
+ import { createInterface } from 'node:readline/promises';
2
+ import { stdin as input, stdout as output } from 'node:process';
1
3
  import { createServer } from 'node:http';
2
4
  import { randomUUID } from 'node:crypto';
3
5
  import { URL } from 'node:url';
4
6
  import open from 'open';
5
7
  import { brand, logo } from '../output/theme.js';
6
8
  const TIMEOUT_MS = 120_000;
9
+ const CLI_TOKEN_PATTERN = /^fb_cli_[a-f0-9]{64}$/;
10
+ export function isCliTokenFormat(token) {
11
+ return CLI_TOKEN_PATTERN.test(token);
12
+ }
7
13
  const SUCCESS_HTML = `<!DOCTYPE html>
8
14
  <html><head><title>FeedbackBasket CLI</title>
9
15
  <style>
@@ -102,10 +108,44 @@ export async function browserLogin(baseUrl, scope = 'read') {
102
108
  console.log(` ${brand.primary(authorizeUrl)}`);
103
109
  console.log();
104
110
  console.log(` ${brand.muted('Waiting for authentication...')}`);
111
+ console.log(` ${brand.muted('If this machine cannot receive the localhost browser callback, use:')}`);
112
+ console.log(` ${brand.command('feedbackbasket login --manual')}`);
105
113
  console.log();
106
114
  open(authorizeUrl).catch(() => {
107
- // Browser open failed user will use the URL manually
115
+ // Browser open failed; user will use the URL manually.
108
116
  });
109
117
  });
110
118
  });
111
119
  }
120
+ export async function manualLogin(baseUrl, scope = 'read') {
121
+ const authorizeUrl = `${baseUrl}/cli/authorize?mode=manual&scope=${scope}`;
122
+ console.log();
123
+ console.log(` ${logo()} CLI`);
124
+ console.log();
125
+ console.log(` ${brand.primary('Open this URL on any machine with a browser:')}`);
126
+ console.log();
127
+ console.log(` ${brand.primary(authorizeUrl)}`);
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();
132
+ console.log(` ${brand.muted('After approving access, paste the token shown in your browser.')}`);
133
+ console.log();
134
+ open(authorizeUrl).catch(() => {
135
+ // Browser open failed; user will use the URL manually.
136
+ });
137
+ const rl = createInterface({ input, output });
138
+ try {
139
+ const token = (await rl.question(' Paste token: ')).trim();
140
+ if (!token) {
141
+ throw new Error('No token provided');
142
+ }
143
+ if (!isCliTokenFormat(token)) {
144
+ throw new Error('Expected a FeedbackBasket CLI token beginning with fb_cli_');
145
+ }
146
+ return { token, scope };
147
+ }
148
+ finally {
149
+ rl.close();
150
+ }
151
+ }
@@ -16,6 +16,7 @@ export declare class FeedbackBasketClient {
16
16
  name?: string;
17
17
  url?: string;
18
18
  description?: string;
19
+ replyToEmail?: string | null;
19
20
  }): Promise<Project>;
20
21
  deleteProject(id: string): Promise<{
21
22
  deleted: boolean;
@@ -59,6 +60,26 @@ export declare class FeedbackBasketClient {
59
60
  content: string;
60
61
  createdAt: string;
61
62
  }>;
63
+ sendReply(feedbackId: string, content: string, replyToEmail?: string): Promise<{
64
+ reply: {
65
+ id: string;
66
+ content: string;
67
+ replyToEmail: string;
68
+ sentBy: string;
69
+ createdAt: string;
70
+ };
71
+ sentTo: string;
72
+ }>;
73
+ listReplies(feedbackId: string): Promise<{
74
+ replies: Array<{
75
+ id: string;
76
+ content: string;
77
+ replyToEmail: string;
78
+ sentBy: string;
79
+ createdAt: string;
80
+ }>;
81
+ total: number;
82
+ }>;
62
83
  deleteFeedback(id: string): Promise<{
63
84
  deleted: boolean;
64
85
  id: string;
@@ -67,6 +67,15 @@ export class FeedbackBasketClient {
67
67
  async createNote(feedbackId, content) {
68
68
  return this.request('POST', `/feedback/${encodeURIComponent(feedbackId)}/notes`, { content });
69
69
  }
70
+ async sendReply(feedbackId, content, replyToEmail) {
71
+ const body = { content };
72
+ if (replyToEmail)
73
+ body['replyToEmail'] = replyToEmail;
74
+ return this.request('POST', `/feedback/${encodeURIComponent(feedbackId)}/replies`, body);
75
+ }
76
+ async listReplies(feedbackId) {
77
+ return this.request('GET', `/feedback/${encodeURIComponent(feedbackId)}/replies`);
78
+ }
70
79
  async deleteFeedback(id) {
71
80
  return this.request('DELETE', `/feedback/${encodeURIComponent(id)}`);
72
81
  }
@@ -3,7 +3,7 @@ import { existsSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
4
  import { homedir } from 'node:os';
5
5
  import { AuthManager } from '../auth/manager.js';
6
- import { browserLogin } from '../auth/login.js';
6
+ import { browserLogin, isCliTokenFormat, manualLogin } from '../auth/login.js';
7
7
  import { saveCredentials } from '../config/credentials.js';
8
8
  import { loadConfig, saveConfig } from '../config/config.js';
9
9
  import { FeedbackBasketClient } from '../client.js';
@@ -18,6 +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', 'Authenticate without a localhost browser callback')
21
22
  .option('--scope <scope>', 'Access scope: read or full', 'full')
22
23
  .action(async (opts) => {
23
24
  const writer = getWriter();
@@ -36,10 +37,17 @@ export function createAuthCommand(getWriter) {
36
37
  if (opts.token) {
37
38
  token = opts.token;
38
39
  }
40
+ else if (opts.manual) {
41
+ const result = await manualLogin(config.baseUrl, scope);
42
+ token = result.token;
43
+ }
39
44
  else {
40
45
  const result = await browserLogin(config.baseUrl, scope);
41
46
  token = result.token;
42
47
  }
48
+ if (!isCliTokenFormat(token)) {
49
+ throw errUsage('Expected a FeedbackBasket CLI token beginning with fb_cli_', 'MCP API keys begin with fb_key_ and are only for MCP server configuration');
50
+ }
43
51
  // Verify token + get profile
44
52
  const client = new FeedbackBasketClient(token, config.baseUrl);
45
53
  let email;
@@ -291,6 +299,7 @@ export function createLoginCommand(getWriter) {
291
299
  return new Command('login')
292
300
  .description('Authenticate with FeedbackBasket (alias for auth login)')
293
301
  .option('--token <token>', 'Use an API token directly (for CI/headless)')
302
+ .option('--manual', 'Authenticate without a localhost browser callback')
294
303
  .option('--scope <scope>', 'Access scope: read or full', 'full')
295
304
  .action(async (opts) => {
296
305
  // Delegate to auth login by re-parsing
@@ -298,6 +307,8 @@ export function createLoginCommand(getWriter) {
298
307
  const args = ['node', 'feedbackbasket', 'login'];
299
308
  if (opts.token)
300
309
  args.push('--token', opts.token);
310
+ if (opts.manual)
311
+ args.push('--manual');
301
312
  if (opts.scope)
302
313
  args.push('--scope', opts.scope);
303
314
  await authCmd.parseAsync(args);
@@ -0,0 +1,4 @@
1
+ import { Command } from 'commander';
2
+ import type { OutputWriter } from '../output/writer.js';
3
+ export declare function createFeedbackReplyCommand(getWriter: () => OutputWriter): Command;
4
+ export declare function createFeedbackRepliesCommand(getWriter: () => OutputWriter): Command;
@@ -0,0 +1,131 @@
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 { ask, confirm } from '../prompt.js';
8
+ export function createFeedbackReplyCommand(getWriter) {
9
+ return new Command('reply')
10
+ .argument('<id>', 'Feedback ID to reply to')
11
+ .argument('[content]', 'Reply content (or use --content)')
12
+ .description('Send an email reply to the feedback submitter')
13
+ .option('--content <text>', 'Reply content (alternative to positional argument)')
14
+ .option('--reply-to <email>', 'Reply-to email (overrides project default)')
15
+ .action(async (id, contentArg, opts) => {
16
+ const writer = getWriter();
17
+ const content = contentArg ?? opts.content;
18
+ if (!content) {
19
+ throw errUsage('Reply content is required', 'Example: feedbackbasket feedback reply <id> "Thanks for reporting this!"');
20
+ }
21
+ const client = requireClient();
22
+ let replyTo = opts.replyTo;
23
+ // If no --reply-to flag, check if we need to resolve one interactively
24
+ if (!replyTo) {
25
+ const feedback = await client.getFeedbackById(id);
26
+ if (!feedback.email) {
27
+ throw errUsage('This feedback has no email address — cannot send a reply.');
28
+ }
29
+ const projectReplyTo = feedback.project.replyToEmail;
30
+ if (!projectReplyTo) {
31
+ const isInteractive = !writer.isMachineOutput() && process.stdin.isTTY;
32
+ if (isInteractive) {
33
+ // Get user's email as suggestion
34
+ const manager = new AuthManager();
35
+ const creds = manager.getCredentials();
36
+ const accountEmail = creds?.email;
37
+ console.log();
38
+ console.log(brand.warning(' No reply-to email configured for this project.'));
39
+ console.log(brand.muted(' The recipient will see this as the sender address.'));
40
+ console.log();
41
+ if (accountEmail) {
42
+ const useAccount = await confirm(` Use ${brand.bold(accountEmail)}?`);
43
+ if (useAccount) {
44
+ replyTo = accountEmail;
45
+ }
46
+ else {
47
+ replyTo = await ask(' Enter reply-to email: ');
48
+ if (!replyTo || !replyTo.includes('@')) {
49
+ console.log(brand.muted(' Cancelled.'));
50
+ return;
51
+ }
52
+ }
53
+ }
54
+ else {
55
+ replyTo = await ask(' Enter reply-to email: ');
56
+ if (!replyTo || !replyTo.includes('@')) {
57
+ console.log(brand.muted(' Cancelled.'));
58
+ return;
59
+ }
60
+ }
61
+ console.log();
62
+ console.log(brand.muted(` Tip: Set a default with: feedbackbasket projects update ${feedback.project.name} --reply-to ${replyTo}`));
63
+ console.log();
64
+ }
65
+ else {
66
+ // Agent mode — must pass --reply-to or set project default
67
+ throw errUsage('No reply-to email configured for this project.', `Pass --reply-to <email> or set a default: feedbackbasket projects update <project> --reply-to <email>`);
68
+ }
69
+ }
70
+ }
71
+ const result = await client.sendReply(id, content, replyTo);
72
+ if (!writer.isMachineOutput()) {
73
+ console.log(` ${brand.success('✓')} Reply sent to ${brand.bold(result.sentTo)}`);
74
+ console.log(` ${brand.muted('From:')} ${result.reply.replyToEmail}`);
75
+ console.log(` ${brand.muted('By:')} ${result.reply.sentBy}`);
76
+ console.log();
77
+ }
78
+ writer.ok(result, {
79
+ summary: `Reply sent to ${result.sentTo}`,
80
+ breadcrumbs: [
81
+ { action: 'View replies', cmd: `feedbackbasket feedback replies ${id}` },
82
+ { action: 'Update status', cmd: `feedbackbasket feedback update ${id} --status COMPLETE` },
83
+ { action: 'Add note', cmd: `feedbackbasket feedback note ${id} "<note>"` },
84
+ ],
85
+ });
86
+ });
87
+ }
88
+ export function createFeedbackRepliesCommand(getWriter) {
89
+ return new Command('replies')
90
+ .argument('<id>', 'Feedback ID')
91
+ .description('List all replies sent for a feedback item')
92
+ .action(async (id) => {
93
+ const writer = getWriter();
94
+ const client = requireClient();
95
+ const result = await client.listReplies(id);
96
+ if (!writer.isMachineOutput()) {
97
+ if (result.replies.length === 0) {
98
+ console.log(brand.muted(' No replies sent yet.'));
99
+ console.log();
100
+ }
101
+ else {
102
+ console.log(brand.bold(`${result.total} repl${result.total === 1 ? 'y' : 'ies'} for feedback ${id}`));
103
+ console.log();
104
+ for (const r of result.replies) {
105
+ console.log(` ${brand.success('→')} ${brand.bold(r.sentBy)} ${brand.muted(r.createdAt)}`);
106
+ console.log(` ${brand.muted('Reply-to:')} ${r.replyToEmail}`);
107
+ console.log();
108
+ for (const line of r.content.split('\n')) {
109
+ console.log(` ${line}`);
110
+ }
111
+ console.log();
112
+ }
113
+ }
114
+ }
115
+ writer.ok(result.replies, {
116
+ summary: `${result.total} repl${result.total === 1 ? 'y' : 'ies'}`,
117
+ breadcrumbs: [
118
+ { action: 'Send a reply', cmd: `feedbackbasket feedback reply ${id} "<content>"` },
119
+ { action: 'View feedback', cmd: `feedbackbasket feedback show ${id}` },
120
+ ],
121
+ });
122
+ });
123
+ }
124
+ function requireClient() {
125
+ const manager = new AuthManager();
126
+ const token = manager.resolveToken();
127
+ if (!token)
128
+ throw errAuth();
129
+ const config = loadConfig();
130
+ return new FeedbackBasketClient(token, config.baseUrl);
131
+ }
@@ -20,12 +20,15 @@ import { createFeedbackNoteCommand } from './feedback-note.js';
20
20
  import { createFeedbackDeleteCommand } from './feedback-delete.js';
21
21
  import { createFeedbackBulkUpdateCommand } from './feedback-bulk-update.js';
22
22
  import { createFeedbackExportCommand } from './feedback-export.js';
23
+ import { createFeedbackReplyCommand, createFeedbackRepliesCommand } from './feedback-reply.js';
23
24
  export function createFeedbackCommand(getWriter) {
24
25
  const feedback = new Command('feedback')
25
26
  .description('View and manage feedback');
26
27
  // Write subcommands
27
28
  feedback.addCommand(createFeedbackUpdateCommand(getWriter));
28
29
  feedback.addCommand(createFeedbackNoteCommand(getWriter));
30
+ feedback.addCommand(createFeedbackReplyCommand(getWriter));
31
+ feedback.addCommand(createFeedbackRepliesCommand(getWriter));
29
32
  feedback.addCommand(createFeedbackDeleteCommand(getWriter));
30
33
  feedback.addCommand(createFeedbackBulkUpdateCommand(getWriter));
31
34
  feedback.addCommand(createFeedbackExportCommand(getWriter));
@@ -88,10 +88,14 @@ export function createProjectsCommand(getWriter) {
88
88
  .option('--name <name>', 'New project name')
89
89
  .option('--url <url>', 'New project URL')
90
90
  .option('--description <text>', 'New project description')
91
+ .option('--reply-to <email>', 'Default reply-to email for feedback replies (empty string to clear)')
91
92
  .action(async (idOrName, opts) => {
92
93
  const writer = getWriter();
93
- if (!opts.name && !opts.url && opts.description === undefined) {
94
- throw errUsage('At least one of --name, --url, or --description is required', 'Example: feedbackbasket projects update <id> --name "New Name"');
94
+ if (!opts.name &&
95
+ !opts.url &&
96
+ opts.description === undefined &&
97
+ opts.replyTo === undefined) {
98
+ throw errUsage('At least one of --name, --url, --description, or --reply-to is required', 'Example: feedbackbasket projects update <id> --name "New Name"');
95
99
  }
96
100
  const client = requireClient();
97
101
  const resolved = await resolveProject(client, idOrName);
@@ -103,6 +107,8 @@ export function createProjectsCommand(getWriter) {
103
107
  data['url'] = opts.url;
104
108
  if (opts.description !== undefined)
105
109
  data['description'] = opts.description;
110
+ if (opts.replyTo !== undefined)
111
+ data['replyToEmail'] = opts.replyTo || null;
106
112
  const updated = await client.updateProject(id, data);
107
113
  if (!writer.isMachineOutput()) {
108
114
  console.log(` ${brand.success('✓')} Project updated: ${brand.bold(updated.name)}`);
@@ -196,6 +202,9 @@ function renderProjectDetail(project) {
196
202
  if (project.description) {
197
203
  console.log(` ${brand.label('Description'.padEnd(14))} ${project.description}`);
198
204
  }
205
+ if (project.replyToEmail) {
206
+ console.log(` ${brand.label('Reply-to'.padEnd(14))} ${project.replyToEmail}`);
207
+ }
199
208
  console.log(` ${brand.label('Created'.padEnd(14))} ${project.createdAt}`);
200
209
  console.log(` ${brand.label('Feedback'.padEnd(14))} ${project.totalFeedback}`);
201
210
  const statusEntries = Object.entries(project.byStatus);
@@ -7,6 +7,7 @@ export interface Project {
7
7
  name: string;
8
8
  url: string;
9
9
  description?: string;
10
+ replyToEmail?: string | null;
10
11
  createdAt: string;
11
12
  totalFeedback: number;
12
13
  byStatus: Record<string, number>;
@@ -30,6 +31,7 @@ export interface Feedback {
30
31
  project: {
31
32
  id: string;
32
33
  name: string;
34
+ replyToEmail?: string | null;
33
35
  };
34
36
  notes?: FeedbackNote[];
35
37
  createdAt: string;
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.3.6";
2
- export declare const USER_AGENT = "FeedbackBasket-CLI/0.3.6";
1
+ export declare const VERSION = "0.5.0";
2
+ export declare const USER_AGENT = "FeedbackBasket-CLI/0.5.0";
@@ -1,2 +1,2 @@
1
- export const VERSION = '0.3.6';
1
+ export const VERSION = '0.5.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.3.6",
3
+ "version": "0.5.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",
@@ -28,7 +28,7 @@
28
28
  },
29
29
  "homepage": "https://feedbackbasket.com",
30
30
  "dependencies": {
31
- "axios": "^1.7.0",
31
+ "axios": "1.7.0",
32
32
  "chalk": "^5.3.0",
33
33
  "commander": "^13.1.0",
34
34
  "open": "^10.1.0"
@@ -1,74 +1,160 @@
1
1
  ---
2
2
  name: FeedbackBasket
3
- description: Manage FeedbackBasket projects, feedback, and bug reports from the command line
3
+ description: Manage FeedbackBasket projects, feedback, bugs, widgets, and team from the command line
4
4
  triggers:
5
5
  - feedbackbasket
6
6
  - feedback
7
7
  - bugs
8
8
  - bug reports
9
9
  - user feedback
10
+ - widget
11
+ - feedback widget
10
12
  invocable: true
11
13
  argument-hint: "<command> [options]"
12
14
  ---
13
15
 
14
16
  # FeedbackBasket CLI
15
17
 
16
- Command-line interface for managing feedback, bug reports, and projects in FeedbackBasket.
18
+ Full command-line interface for managing feedback, bug reports, projects, widgets, and team in FeedbackBasket. Works with any AI agent that can run shell commands.
17
19
 
18
20
  ## Authentication
19
21
 
20
- Before using any commands, authenticate:
21
22
  ```bash
22
- feedbackbasket auth login # Opens browser for login
23
- feedbackbasket auth login --token <TOKEN> # Manual token (CI/headless)
24
- feedbackbasket auth status # Check auth state
23
+ feedbackbasket login # Opens browser one click, full access
24
+ feedbackbasket login --manual # No localhost browser callback (remote servers)
25
+ feedbackbasket login --token <TOKEN> # Manual token (CI/headless)
26
+ feedbackbasket auth status # Check auth state
27
+ feedbackbasket doctor # Full diagnostics
25
28
  ```
26
29
 
27
30
  ## Output Modes
28
31
 
29
- | Flag | When to Use | Output |
30
- |------|------------|--------|
31
- | (none) | Terminal | Styled, human-readable |
32
- | `--json` | Parse full response | JSON envelope with breadcrumbs |
33
- | `--agent` | Agent automation | Raw JSON data only |
34
- | `--quiet` | Scripting | Raw JSON data only |
35
- | `--md` | Documentation | Markdown formatted |
32
+ | Flag | Output | When to Use |
33
+ |------|--------|-------------|
34
+ | (none) | Styled (TTY) or JSON (piped) | Auto-detect |
35
+ | `--json` | JSON envelope with breadcrumbs | Parse full response |
36
+ | `--agent` | Raw JSON data only | Agent automation |
37
+ | `--quiet` | Raw JSON data only | Scripting |
38
+ | `--md` | Markdown | Documentation |
36
39
 
37
40
  **Agent rule**: Always use `--agent` for programmatic access. Parse the JSON output directly.
38
41
 
39
42
  ## Quick Reference
40
43
 
41
- | Task | Command |
42
- |------|---------|
43
- | List projects | `feedbackbasket projects list` |
44
- | List feedback | `feedbackbasket feedback list` |
45
- | Filter by project | `feedbackbasket feedback list --project <id>` |
46
- | Filter by category | `feedbackbasket feedback list --category BUG` |
47
- | Filter by status | `feedbackbasket feedback list --status OPEN` |
48
- | Search feedback | `feedbackbasket feedback search "query"` |
49
- | View single item | `feedbackbasket feedback show <id>` |
50
- | List bugs | `feedbackbasket bugs list` |
51
- | High severity bugs | `feedbackbasket bugs list --severity high` |
52
- | Bug statistics | `feedbackbasket bugs stats` |
53
- | Update status | `feedbackbasket feedback update <id> --status PLANNED` |
54
- | Add note | `feedbackbasket feedback note <id> "note content"` |
55
- | Health check | `feedbackbasket doctor` |
56
-
57
- ## Common Workflows
44
+ ### Projects
45
+ ```bash
46
+ feedbackbasket projects list
47
+ feedbackbasket projects show <name-or-id>
48
+ feedbackbasket projects create "My App" --url https://myapp.com --description "..."
49
+ feedbackbasket projects update <name-or-id> --name "New Name" --url <url> --description "..."
50
+ feedbackbasket projects update <name-or-id> --reply-to vlad@example.com # default reply-to for feedback replies
51
+ feedbackbasket projects delete <name-or-id> --yes
52
+ ```
53
+
54
+ All project commands accept **name or ID**. Names are matched case-insensitively with fuzzy suggestions on typos.
55
+
56
+ ### Feedback
57
+ ```bash
58
+ # Read
59
+ feedbackbasket feedback list --project <id> --category BUG --status OPEN --sentiment NEGATIVE
60
+ feedbackbasket feedback list --search "login" --limit 50 --offset 0 --notes
61
+ feedbackbasket feedback show <id>
62
+ feedbackbasket feedback search "crash on mobile" --project <id> --limit 10
63
+
64
+ # Write
65
+ feedbackbasket feedback update <id> --status PLANNED --category BUG --sentiment NEGATIVE
66
+ feedbackbasket feedback note <id> "Investigating — appears related to auth flow"
67
+ feedbackbasket feedback delete <id> --yes
68
+ feedbackbasket feedback bulk-update --status CLOSED --ids id1,id2,id3
69
+
70
+ # Reply to submitter via email
71
+ feedbackbasket feedback reply <id> "Thanks for reporting — we pushed a fix!"
72
+ feedbackbasket feedback reply <id> "<content>" --reply-to vlad@example.com # override reply-to
73
+ feedbackbasket feedback replies <id> # list past replies
74
+
75
+ # Export
76
+ feedbackbasket feedback export <project> --format csv
77
+ feedbackbasket feedback export <project> --format md
78
+ feedbackbasket feedback export <project> --format json
79
+ ```
80
+
81
+ ### Bug Reports
82
+ ```bash
83
+ feedbackbasket bugs list --severity high --status OPEN --project <id>
84
+ feedbackbasket bugs stats --project <id>
85
+ ```
86
+
87
+ ### Widget
88
+ ```bash
89
+ # Get embed code (ready to paste into HTML)
90
+ feedbackbasket widget script <project>
91
+
92
+ # View settings
93
+ feedbackbasket widget settings <project>
94
+
95
+ # Customize
96
+ feedbackbasket widget settings <project> --color "#22c55e" --label "Send Feedback"
97
+ feedbackbasket widget settings <project> --position bottom-left --display modal
98
+ feedbackbasket widget settings <project> --email-required --intro "How can we improve?"
99
+ ```
100
+
101
+ ### Team
102
+ ```bash
103
+ feedbackbasket team list
104
+ feedbackbasket team role <memberId> --role admin
105
+ feedbackbasket team remove <memberId> --yes
106
+ ```
107
+
108
+ ### Utilities
109
+ ```bash
110
+ feedbackbasket doctor # Health check (auth, connectivity, skill)
111
+ feedbackbasket setup claude # Install this skill for Claude Code
112
+ ```
113
+
114
+ ## Common Agent Workflows
115
+
116
+ ### Set up a new project end-to-end
117
+ ```bash
118
+ feedbackbasket projects create "My App" --url https://myapp.com --agent
119
+ feedbackbasket widget script "My App" --agent
120
+ # Agent gets the embed code, adds it to the HTML
121
+ feedbackbasket widget settings "My App" --color "#22c55e" --label "Feedback" --agent
122
+ ```
58
123
 
59
124
  ### Triage new feedback
60
125
  ```bash
61
126
  feedbackbasket feedback list --status OPEN --agent
62
- # Review items, then update status:
63
- feedbackbasket feedback update <id> --status UNDER_REVIEW
64
- feedbackbasket feedback note <id> "Reviewing — appears related to auth flow"
127
+ # Review items, then update:
128
+ feedbackbasket feedback update <id> --status UNDER_REVIEW --agent
129
+ feedbackbasket feedback note <id> "Reviewing — appears related to auth flow" --agent
65
130
  ```
66
131
 
67
- ### Investigate bugs
132
+ ### Investigate high-priority bugs
68
133
  ```bash
69
134
  feedbackbasket bugs list --severity high --agent
70
135
  feedbackbasket feedback show <id> --agent
71
- # Shows full details including browser, OS, page URL, AI analysis
136
+ # Response includes browser, OS, page URL, AI analysis, priority score
137
+ ```
138
+
139
+ ### Close the loop — reply to the submitter
140
+ ```bash
141
+ # Agent reads context, drafts its own reply, sends it
142
+ feedbackbasket feedback show <id> --agent # read context + project.replyToEmail
143
+ feedbackbasket feedback reply <id> "<drafted response>" --agent
144
+ feedbackbasket feedback update <id> --status COMPLETE --agent
145
+ feedbackbasket feedback note <id> "Replied via CLI" --agent
146
+ ```
147
+ **Important:** If `feedback show` returns `project.replyToEmail: null`, the agent MUST either:
148
+ 1. Pass `--reply-to <email>` with an explicit address, OR
149
+ 2. Ask the human which reply-to email to use (the account owner's email is a reasonable default, but requires user confirmation), OR
150
+ 3. Set a project default first: `feedbackbasket projects update <project> --reply-to <email>`
151
+
152
+ Never silently guess a reply-to address — it becomes the "From" address the customer sees.
153
+
154
+ ### Export for analysis
155
+ ```bash
156
+ feedbackbasket feedback export myapp --format json --agent
157
+ # Agent can parse the JSON and generate reports
72
158
  ```
73
159
 
74
160
  ### Search for patterns
@@ -79,41 +165,28 @@ feedbackbasket feedback search "crash" --category BUG --agent
79
165
 
80
166
  ## Filtering Options
81
167
 
82
- ### Categories
83
- `BUG`, `FEATURE_REQUEST`, `IMPROVEMENT`, `QUESTION`
84
-
85
- ### Statuses
86
- `OPEN`, `UNDER_REVIEW`, `PLANNED`, `IN_PROGRESS`, `COMPLETE`, `CLOSED`
87
-
88
- ### Sentiments
89
- `POSITIVE`, `NEGATIVE`, `NEUTRAL`
90
-
91
- ### Bug Severity
92
- `high`, `medium`, `low`
93
-
94
- ## Pagination
95
-
96
- Use `--limit` and `--offset` for pagination:
97
- ```bash
98
- feedbackbasket feedback list --limit 50 --offset 0
99
- feedbackbasket feedback list --limit 50 --offset 50 # next page
100
- ```
168
+ | Type | Values |
169
+ |------|--------|
170
+ | Categories | `BUG`, `FEATURE_REQUEST`, `IMPROVEMENT`, `QUESTION` |
171
+ | Statuses | `OPEN`, `UNDER_REVIEW`, `PLANNED`, `IN_PROGRESS`, `COMPLETE`, `CLOSED` |
172
+ | Sentiments | `POSITIVE`, `NEGATIVE`, `NEUTRAL` |
173
+ | Bug Severity | `high`, `medium`, `low` |
101
174
 
102
- ## JSON Envelope Format
175
+ ## JSON Envelope
103
176
 
104
- When using `--json`, responses follow this structure:
177
+ When using `--json`, responses include breadcrumbs:
105
178
  ```json
106
179
  {
107
180
  "ok": true,
108
181
  "data": [...],
109
- "summary": "Showing 20 of 156 feedback items",
182
+ "summary": "5 projects",
110
183
  "breadcrumbs": [
111
- { "action": "Next page", "cmd": "feedbackbasket feedback list --offset 20" }
184
+ { "action": "View feedback", "cmd": "feedbackbasket feedback list --project myapp" }
112
185
  ]
113
186
  }
114
187
  ```
115
188
 
116
- Errors:
189
+ Errors include hints:
117
190
  ```json
118
191
  {
119
192
  "ok": false,
@@ -126,7 +199,10 @@ Errors:
126
199
  ## Invariants
127
200
 
128
201
  - Always authenticate before data commands
129
- - `--agent` flag suppresses all interactive prompts
130
- - Write operations (update, note) require `--scope full` during auth
131
- - Feedback IDs are stable safe to reference across commands
202
+ - `--agent` flag suppresses all interactive prompts and confirmations
203
+ - Default project (set during login) is used when `--project` is not specified
204
+ - Project names resolve case-insensitively with fuzzy matching
205
+ - Write operations use full scope (granted by default during login)
206
+ - Feedback IDs are stable CUIDs — safe to reference across commands
132
207
  - All timestamps are ISO 8601
208
+ - `--yes` flag skips delete confirmations in interactive mode