feedbackbasket-cli 0.3.5 → 0.4.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 +10 -1
- package/dist/src/auth/login.d.ts +2 -0
- package/dist/src/auth/login.js +37 -1
- package/dist/src/client.d.ts +21 -0
- package/dist/src/client.js +9 -0
- package/dist/src/commands/auth.js +12 -1
- package/dist/src/commands/feedback-reply.d.ts +4 -0
- package/dist/src/commands/feedback-reply.js +131 -0
- package/dist/src/commands/feedback.js +3 -0
- package/dist/src/commands/projects.js +11 -2
- package/dist/src/types.d.ts +2 -0
- package/dist/src/version.d.ts +2 -2
- package/dist/src/version.js +1 -1
- package/package.json +2 -2
- package/skills/feedbackbasket/SKILL.md +138 -62
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/headless browser flow (paste token back into terminal)
|
|
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 --
|
|
54
|
+
feedbackbasket login --manual # Remote/headless browser token paste
|
|
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
|
package/dist/src/auth/login.d.ts
CHANGED
|
@@ -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 {};
|
package/dist/src/auth/login.js
CHANGED
|
@@ -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,40 @@ 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('On remote machines, use: feedbackbasket login --manual')}`);
|
|
105
112
|
console.log();
|
|
106
113
|
open(authorizeUrl).catch(() => {
|
|
107
|
-
// Browser open failed
|
|
114
|
+
// Browser open failed; user will use the URL manually.
|
|
108
115
|
});
|
|
109
116
|
});
|
|
110
117
|
});
|
|
111
118
|
}
|
|
119
|
+
export async function manualLogin(baseUrl, scope = 'read') {
|
|
120
|
+
const authorizeUrl = `${baseUrl}/cli/authorize?mode=manual&scope=${scope}`;
|
|
121
|
+
console.log();
|
|
122
|
+
console.log(` ${logo()} CLI`);
|
|
123
|
+
console.log();
|
|
124
|
+
console.log(` ${brand.primary('Open this URL to create a CLI token:')}`);
|
|
125
|
+
console.log();
|
|
126
|
+
console.log(` ${brand.primary(authorizeUrl)}`);
|
|
127
|
+
console.log();
|
|
128
|
+
console.log(` ${brand.muted('After approving access, paste the token shown in your browser.')}`);
|
|
129
|
+
console.log();
|
|
130
|
+
open(authorizeUrl).catch(() => {
|
|
131
|
+
// Browser open failed; user will use the URL manually.
|
|
132
|
+
});
|
|
133
|
+
const rl = createInterface({ input, output });
|
|
134
|
+
try {
|
|
135
|
+
const token = (await rl.question(' Paste token: ')).trim();
|
|
136
|
+
if (!token) {
|
|
137
|
+
throw new Error('No token provided');
|
|
138
|
+
}
|
|
139
|
+
if (!isCliTokenFormat(token)) {
|
|
140
|
+
throw new Error('Expected a FeedbackBasket CLI token beginning with fb_cli_');
|
|
141
|
+
}
|
|
142
|
+
return { token, scope };
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
rl.close();
|
|
146
|
+
}
|
|
147
|
+
}
|
package/dist/src/client.d.ts
CHANGED
|
@@ -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;
|
package/dist/src/client.js
CHANGED
|
@@ -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', 'Show a browser token to paste back into the terminal')
|
|
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', 'Show a browser token to paste back into the terminal')
|
|
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 &&
|
|
94
|
-
|
|
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);
|
package/dist/src/types.d.ts
CHANGED
|
@@ -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;
|
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.4.0";
|
|
2
|
+
export declare const USER_AGENT = "FeedbackBasket-CLI/0.4.0";
|
package/dist/src/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const VERSION = '0.
|
|
1
|
+
export const VERSION = '0.4.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
|
+
"version": "0.4.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": "
|
|
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
|
|
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
|
-
|
|
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
|
|
23
|
-
feedbackbasket
|
|
24
|
-
feedbackbasket
|
|
23
|
+
feedbackbasket login # Opens browser — one click, full access
|
|
24
|
+
feedbackbasket login --manual # Remote/headless browser token paste
|
|
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 |
|
|
30
|
-
|
|
31
|
-
| (none) |
|
|
32
|
-
| `--json` |
|
|
33
|
-
| `--agent` |
|
|
34
|
-
| `--quiet` |
|
|
35
|
-
| `--md` |
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
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
|
-
#
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
|
175
|
+
## JSON Envelope
|
|
103
176
|
|
|
104
|
-
When using `--json`, responses
|
|
177
|
+
When using `--json`, responses include breadcrumbs:
|
|
105
178
|
```json
|
|
106
179
|
{
|
|
107
180
|
"ok": true,
|
|
108
181
|
"data": [...],
|
|
109
|
-
"summary": "
|
|
182
|
+
"summary": "5 projects",
|
|
110
183
|
"breadcrumbs": [
|
|
111
|
-
{ "action": "
|
|
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
|
-
-
|
|
131
|
-
-
|
|
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
|