feedbackbasket-cli 3.1.0 → 3.2.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/CHANGELOG.md +11 -0
- package/README.md +5 -3
- package/dist/src/client.d.ts +7 -1
- package/dist/src/client.js +21 -12
- package/dist/src/commands/feedback-bulk-update.js +21 -5
- package/dist/src/commands/feedback-create.js +26 -3
- package/dist/src/commands/feedback-update.js +26 -2
- package/dist/src/commands/feedback.js +30 -9
- package/dist/src/types.d.ts +7 -0
- package/dist/src/version.d.ts +1 -1
- package/package.json +2 -2
- package/skills/feedbackbasket/SKILL.md +14 -9
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## [3.2.0] - 2026-09-04
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added structured close reasons and optional internal close notes to feedback create, update, bulk update, list, and show commands.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Closing feedback now requires `--close-reason`. The `OTHER` reason also requires `--close-note`.
|
|
14
|
+
- The CLI and both MCP transports now use agent surface version `3.2.0`.
|
|
15
|
+
|
|
5
16
|
## [3.1.0] - 2026-08-22
|
|
6
17
|
|
|
7
18
|
### Added
|
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ The browser selects the organization, Read or Full access, and Selected projects
|
|
|
30
30
|
|
|
31
31
|
The first time you log in, a setup wizard walks you through selecting a default project and installing the Claude Code skill.
|
|
32
32
|
|
|
33
|
-
The CLI uses agent surface version `3.
|
|
33
|
+
The CLI uses agent surface version `3.2.0`. CLI login accepts only private CLI credentials. It does not accept MCP keys or OAuth tokens. Use `--yes` for high-impact commands in agent or machine mode. Interactive use can show a confirmation prompt. Never put an access token, refresh token, CLI token, or MCP key in source, prompts, logs, generated configuration, or final output.
|
|
34
34
|
|
|
35
35
|
## Agent Usage
|
|
36
36
|
|
|
@@ -57,7 +57,7 @@ feedbackbasket setup claude
|
|
|
57
57
|
<!-- BEGIN GENERATED AGENT CAPABILITIES -->
|
|
58
58
|
## Agent capability contract
|
|
59
59
|
|
|
60
|
-
Agent surface version: `3.
|
|
60
|
+
Agent surface version: `3.2.0`. The CLI and both MCP transports implement the same 31 product operations.
|
|
61
61
|
|
|
62
62
|
| Product operation | CLI command | MCP tool | Required access | Confirm |
|
|
63
63
|
| --- | --- | --- | --- | --- |
|
|
@@ -135,6 +135,7 @@ feedbackbasket feedback list --category BUG # Filter by category
|
|
|
135
135
|
feedbackbasket feedback list --status OPEN # Filter by status
|
|
136
136
|
feedbackbasket feedback list --sentiment NEGATIVE # Filter by sentiment
|
|
137
137
|
feedbackbasket feedback list --search "login issue" # Text search
|
|
138
|
+
feedbackbasket feedback list --status CLOSED --close-reason NOT_PLANNED
|
|
138
139
|
feedbackbasket feedback show <id> # View detail, including attachment links
|
|
139
140
|
feedbackbasket feedback search "crash on mobile" # Search shortcut
|
|
140
141
|
|
|
@@ -142,6 +143,7 @@ feedbackbasket feedback search "crash on mobile" # Search shortcut
|
|
|
142
143
|
feedbackbasket feedback create "Title" --content "Body" --project myapp
|
|
143
144
|
feedbackbasket feedback create "Login bug" --content "Clicking Log in does nothing" --project myapp --type bug --page-url https://example.com/login
|
|
144
145
|
feedbackbasket feedback update <id> --status PLANNED # Update status
|
|
146
|
+
feedbackbasket feedback update <id> --status CLOSED --close-reason NOT_PLANNED
|
|
145
147
|
feedbackbasket feedback update <id> --category BUG # Update category
|
|
146
148
|
feedbackbasket feedback reply <id> "Thanks!" --delivery email --reply-to support@example.com
|
|
147
149
|
feedbackbasket feedback reply <id> "Thanks!" --delivery widget
|
|
@@ -150,7 +152,7 @@ feedbackbasket feedback reply <id> "Thanks!" --delivery both --reply-to support@
|
|
|
150
152
|
feedbackbasket feedback replies <id> # Show the complete conversation
|
|
151
153
|
feedbackbasket feedback note <id> "Investigating this..." # Add internal note
|
|
152
154
|
feedbackbasket feedback delete <id> # Delete feedback
|
|
153
|
-
feedbackbasket feedback bulk-update --status CLOSED --ids id1,id2,id3
|
|
155
|
+
feedbackbasket feedback bulk-update --status CLOSED --close-reason NOT_ACTIONABLE --ids id1,id2,id3
|
|
154
156
|
|
|
155
157
|
# Export
|
|
156
158
|
feedbackbasket feedback export myapp --format csv # Export to CSV
|
package/dist/src/client.d.ts
CHANGED
|
@@ -71,6 +71,8 @@ export declare class FeedbackBasketClient {
|
|
|
71
71
|
status?: string;
|
|
72
72
|
category?: string;
|
|
73
73
|
sentiment?: string;
|
|
74
|
+
closeReason?: string;
|
|
75
|
+
closeNote?: string;
|
|
74
76
|
}): Promise<Feedback>;
|
|
75
77
|
createNote(feedbackId: string, content: string): Promise<{
|
|
76
78
|
id: string;
|
|
@@ -96,9 +98,13 @@ export declare class FeedbackBasketClient {
|
|
|
96
98
|
deleted: boolean;
|
|
97
99
|
id: string;
|
|
98
100
|
}>;
|
|
99
|
-
bulkUpdateStatus(ids: string[], status: string
|
|
101
|
+
bulkUpdateStatus(ids: string[], status: string, closure?: {
|
|
102
|
+
closeReason?: string;
|
|
103
|
+
closeNote?: string;
|
|
104
|
+
}): Promise<{
|
|
100
105
|
updated: number;
|
|
101
106
|
status: string;
|
|
107
|
+
closeReason?: string | null;
|
|
102
108
|
}>;
|
|
103
109
|
updateNote(feedbackId: string, noteId: string, content: string): Promise<{
|
|
104
110
|
id: string;
|
package/dist/src/client.js
CHANGED
|
@@ -99,8 +99,12 @@ export class FeedbackBasketClient {
|
|
|
99
99
|
async deleteFeedback(id) {
|
|
100
100
|
return this.request('DELETE', `/feedback/${encodeURIComponent(id)}`);
|
|
101
101
|
}
|
|
102
|
-
async bulkUpdateStatus(ids, status) {
|
|
103
|
-
return this.request('POST', '/feedback/bulk-update', {
|
|
102
|
+
async bulkUpdateStatus(ids, status, closure = {}) {
|
|
103
|
+
return this.request('POST', '/feedback/bulk-update', {
|
|
104
|
+
ids,
|
|
105
|
+
status,
|
|
106
|
+
...closure,
|
|
107
|
+
});
|
|
104
108
|
}
|
|
105
109
|
async updateNote(feedbackId, noteId, content) {
|
|
106
110
|
return this.request('PATCH', `/feedback/${encodeURIComponent(feedbackId)}/notes/${encodeURIComponent(noteId)}`, { content });
|
|
@@ -117,7 +121,9 @@ export class FeedbackBasketClient {
|
|
|
117
121
|
return this.request('GET', '/team');
|
|
118
122
|
}
|
|
119
123
|
async updateMemberRole(memberId, role) {
|
|
120
|
-
return this.request('PATCH', `/team/${encodeURIComponent(memberId)}`, {
|
|
124
|
+
return this.request('PATCH', `/team/${encodeURIComponent(memberId)}`, {
|
|
125
|
+
role,
|
|
126
|
+
});
|
|
121
127
|
}
|
|
122
128
|
async removeMember(memberId) {
|
|
123
129
|
return this.request('DELETE', `/team/${encodeURIComponent(memberId)}`);
|
|
@@ -130,24 +136,27 @@ export class FeedbackBasketClient {
|
|
|
130
136
|
method,
|
|
131
137
|
signal: controller.signal,
|
|
132
138
|
headers: {
|
|
133
|
-
|
|
139
|
+
Authorization: `Bearer ${this.token}`,
|
|
134
140
|
'Content-Type': 'application/json',
|
|
135
141
|
'User-Agent': USER_AGENT,
|
|
136
142
|
},
|
|
137
143
|
body: data === undefined ? undefined : JSON.stringify(data),
|
|
138
144
|
});
|
|
139
145
|
const contentType = response.headers.get('content-type') ?? '';
|
|
140
|
-
const payload = contentType.includes('application/json')
|
|
141
|
-
? await response.json().catch(() => null)
|
|
142
|
-
: await response.text();
|
|
146
|
+
const payload = contentType.includes('application/json') ? await response.json().catch(() => null) : await response.text();
|
|
143
147
|
if (!response.ok) {
|
|
144
148
|
const message = getErrorMessage(payload, response.statusText);
|
|
145
149
|
switch (response.status) {
|
|
146
|
-
case 401:
|
|
147
|
-
|
|
148
|
-
case
|
|
149
|
-
|
|
150
|
-
|
|
150
|
+
case 401:
|
|
151
|
+
throw errAuth(message);
|
|
152
|
+
case 403:
|
|
153
|
+
throw errForbidden(message);
|
|
154
|
+
case 404:
|
|
155
|
+
throw errAPI(404, message);
|
|
156
|
+
case 429:
|
|
157
|
+
throw errRateLimit();
|
|
158
|
+
default:
|
|
159
|
+
throw errAPI(response.status, message);
|
|
151
160
|
}
|
|
152
161
|
}
|
|
153
162
|
return payload;
|
|
@@ -5,30 +5,46 @@ import { loadConfig } from '../config/config.js';
|
|
|
5
5
|
import { errAuth, errUsage } from '../output/errors.js';
|
|
6
6
|
import { brand } from '../output/theme.js';
|
|
7
7
|
import { requireHighImpactConfirmation } from '../confirmation.js';
|
|
8
|
+
const validCloseReasons = new Set(['DUPLICATE', 'NOT_PLANNED', 'COULD_NOT_REPRODUCE', 'NOT_ACTIONABLE', 'NO_LONGER_RELEVANT', 'SPAM', 'OTHER']);
|
|
8
9
|
export function createFeedbackBulkUpdateCommand(getWriter) {
|
|
9
10
|
return new Command('bulk-update')
|
|
10
11
|
.description('Update status for multiple feedback items at once')
|
|
11
12
|
.requiredOption('--status <status>', 'New status (OPEN, UNDER_REVIEW, PLANNED, IN_PROGRESS, COMPLETE, CLOSED)')
|
|
13
|
+
.option('--close-reason <reason>', 'Reason for closing (DUPLICATE, NOT_PLANNED, COULD_NOT_REPRODUCE, NOT_ACTIONABLE, NO_LONGER_RELEVANT, SPAM, OTHER)')
|
|
14
|
+
.option('--close-note <note>', 'Internal closure note; required when the reason is OTHER')
|
|
12
15
|
.requiredOption('--ids <ids>', 'Comma-separated feedback IDs')
|
|
13
16
|
.option('--yes', 'Confirm the bulk update')
|
|
14
17
|
.action(async (opts) => {
|
|
15
18
|
const writer = getWriter();
|
|
16
19
|
const client = requireClient();
|
|
17
|
-
const ids = opts.ids
|
|
20
|
+
const ids = opts.ids
|
|
21
|
+
.split(',')
|
|
22
|
+
.map((id) => id.trim())
|
|
23
|
+
.filter(Boolean);
|
|
18
24
|
if (ids.length === 0) {
|
|
19
25
|
throw errUsage('At least one ID is required', 'Example: --ids id1,id2,id3');
|
|
20
26
|
}
|
|
27
|
+
if (opts.status === 'CLOSED' && !opts.closeReason) {
|
|
28
|
+
throw errUsage('--close-reason is required when status is CLOSED');
|
|
29
|
+
}
|
|
30
|
+
if (opts.closeReason && !validCloseReasons.has(opts.closeReason)) {
|
|
31
|
+
throw errUsage('Invalid close reason. Must be one of: DUPLICATE, NOT_PLANNED, COULD_NOT_REPRODUCE, NOT_ACTIONABLE, NO_LONGER_RELEVANT, SPAM, OTHER');
|
|
32
|
+
}
|
|
33
|
+
if (opts.closeReason === 'OTHER' && !opts.closeNote?.trim()) {
|
|
34
|
+
throw errUsage('--close-note is required when --close-reason is OTHER');
|
|
35
|
+
}
|
|
21
36
|
await requireHighImpactConfirmation(writer, Boolean(opts.yes), `Update ${ids.length} feedback item${ids.length === 1 ? '' : 's'}?`, '--yes is required for a bulk update in machine mode.');
|
|
22
|
-
const result = await client.bulkUpdateStatus(ids, opts.status
|
|
37
|
+
const result = await client.bulkUpdateStatus(ids, opts.status, {
|
|
38
|
+
closeReason: opts.closeReason,
|
|
39
|
+
closeNote: opts.closeNote,
|
|
40
|
+
});
|
|
23
41
|
if (!writer.isMachineOutput()) {
|
|
24
42
|
console.log(` ${brand.success('✓')} Updated ${result.updated} feedback items to ${brand.bold(result.status)}`);
|
|
25
43
|
console.log();
|
|
26
44
|
}
|
|
27
45
|
writer.ok(result, {
|
|
28
46
|
summary: `Updated ${result.updated} items to ${result.status}`,
|
|
29
|
-
breadcrumbs: [
|
|
30
|
-
{ action: 'List feedback', cmd: 'feedbackbasket feedback list' },
|
|
31
|
-
],
|
|
47
|
+
breadcrumbs: [{ action: 'List feedback', cmd: 'feedbackbasket feedback list' }],
|
|
32
48
|
});
|
|
33
49
|
});
|
|
34
50
|
}
|
|
@@ -8,6 +8,7 @@ import { resolveProject } from '../resolve.js';
|
|
|
8
8
|
const validTypes = new Set(['bug', 'feature', 'general']);
|
|
9
9
|
const validCategories = new Set(['BUG', 'FEATURE_REQUEST', 'IMPROVEMENT', 'QUESTION']);
|
|
10
10
|
const validStatuses = new Set(['OPEN', 'UNDER_REVIEW', 'PLANNED', 'IN_PROGRESS', 'COMPLETE', 'CLOSED']);
|
|
11
|
+
const validCloseReasons = new Set(['DUPLICATE', 'NOT_PLANNED', 'COULD_NOT_REPRODUCE', 'NOT_ACTIONABLE', 'NO_LONGER_RELEVANT', 'SPAM', 'OTHER']);
|
|
11
12
|
export function createFeedbackCreateCommand(getWriter) {
|
|
12
13
|
return new Command('create')
|
|
13
14
|
.argument('<title>', 'Short title or summary for the feedback')
|
|
@@ -17,6 +18,8 @@ export function createFeedbackCreateCommand(getWriter) {
|
|
|
17
18
|
.option('--type <type>', 'Feedback type (bug, feature, general)')
|
|
18
19
|
.option('--category <category>', 'Category (BUG, FEATURE_REQUEST, IMPROVEMENT, QUESTION)')
|
|
19
20
|
.option('--status <status>', 'Initial status (OPEN, UNDER_REVIEW, PLANNED, IN_PROGRESS, COMPLETE, CLOSED)')
|
|
21
|
+
.option('--close-reason <reason>', 'Reason when the initial status is CLOSED')
|
|
22
|
+
.option('--close-note <note>', 'Internal closure note; required when the reason is OTHER')
|
|
20
23
|
.option('--email <email>', 'Submitter email')
|
|
21
24
|
.option('--page-url <url>', 'Page URL where the feedback applies')
|
|
22
25
|
.option('--metadata <key=value>', 'Metadata key/value pair (repeatable)', collectMetadata, [])
|
|
@@ -32,6 +35,15 @@ export function createFeedbackCreateCommand(getWriter) {
|
|
|
32
35
|
if (opts.status && !validStatuses.has(opts.status)) {
|
|
33
36
|
throw errUsage('Invalid status. Must be one of: OPEN, UNDER_REVIEW, PLANNED, IN_PROGRESS, COMPLETE, CLOSED');
|
|
34
37
|
}
|
|
38
|
+
if (opts.status === 'CLOSED' && !opts.closeReason) {
|
|
39
|
+
throw errUsage('--close-reason is required when status is CLOSED');
|
|
40
|
+
}
|
|
41
|
+
if (opts.closeReason && !validCloseReasons.has(opts.closeReason)) {
|
|
42
|
+
throw errUsage('Invalid close reason. Must be one of: DUPLICATE, NOT_PLANNED, COULD_NOT_REPRODUCE, NOT_ACTIONABLE, NO_LONGER_RELEVANT, SPAM, OTHER');
|
|
43
|
+
}
|
|
44
|
+
if (opts.closeReason === 'OTHER' && !opts.closeNote?.trim()) {
|
|
45
|
+
throw errUsage('--close-note is required when --close-reason is OTHER');
|
|
46
|
+
}
|
|
35
47
|
const project = await resolveProject(client, opts.project);
|
|
36
48
|
const content = composeContent(title, opts.content);
|
|
37
49
|
const metadata = parseMetadata(opts.metadata ?? []);
|
|
@@ -41,6 +53,8 @@ export function createFeedbackCreateCommand(getWriter) {
|
|
|
41
53
|
type: opts.type,
|
|
42
54
|
category: opts.category,
|
|
43
55
|
status: opts.status,
|
|
56
|
+
closeReason: opts.closeReason,
|
|
57
|
+
closeNote: opts.closeNote,
|
|
44
58
|
email: opts.email,
|
|
45
59
|
pageUrl: opts.pageUrl,
|
|
46
60
|
metadata,
|
|
@@ -54,9 +68,18 @@ export function createFeedbackCreateCommand(getWriter) {
|
|
|
54
68
|
writer.ok(result, {
|
|
55
69
|
summary: `Created feedback ${result.id}`,
|
|
56
70
|
breadcrumbs: [
|
|
57
|
-
{
|
|
58
|
-
|
|
59
|
-
|
|
71
|
+
{
|
|
72
|
+
action: 'View feedback',
|
|
73
|
+
cmd: `feedbackbasket feedback show ${result.id}`,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
action: 'Update status',
|
|
77
|
+
cmd: `feedbackbasket feedback update ${result.id} --status UNDER_REVIEW`,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
action: 'List project feedback',
|
|
81
|
+
cmd: `feedbackbasket feedback list --project ${project.id}`,
|
|
82
|
+
},
|
|
60
83
|
],
|
|
61
84
|
});
|
|
62
85
|
});
|
|
@@ -4,11 +4,14 @@ import { AuthManager } from '../auth/manager.js';
|
|
|
4
4
|
import { loadConfig } from '../config/config.js';
|
|
5
5
|
import { errAuth, errUsage } from '../output/errors.js';
|
|
6
6
|
import { brand } from '../output/theme.js';
|
|
7
|
+
const validCloseReasons = new Set(['DUPLICATE', 'NOT_PLANNED', 'COULD_NOT_REPRODUCE', 'NOT_ACTIONABLE', 'NO_LONGER_RELEVANT', 'SPAM', 'OTHER']);
|
|
7
8
|
export function createFeedbackUpdateCommand(getWriter) {
|
|
8
9
|
return new Command('update')
|
|
9
10
|
.argument('<id>', 'Feedback ID to update')
|
|
10
11
|
.description('Update a feedback item')
|
|
11
12
|
.option('--status <status>', 'Set status (OPEN, UNDER_REVIEW, PLANNED, IN_PROGRESS, COMPLETE, CLOSED)')
|
|
13
|
+
.option('--close-reason <reason>', 'Reason for closing (DUPLICATE, NOT_PLANNED, COULD_NOT_REPRODUCE, NOT_ACTIONABLE, NO_LONGER_RELEVANT, SPAM, OTHER)')
|
|
14
|
+
.option('--close-note <note>', 'Internal closure note; required when the reason is OTHER')
|
|
12
15
|
.option('--category <category>', 'Set category (BUG, FEATURE_REQUEST, IMPROVEMENT, QUESTION)')
|
|
13
16
|
.option('--sentiment <sentiment>', 'Set sentiment (POSITIVE, NEGATIVE, NEUTRAL)')
|
|
14
17
|
.action(async (id, opts) => {
|
|
@@ -16,6 +19,15 @@ export function createFeedbackUpdateCommand(getWriter) {
|
|
|
16
19
|
if (!opts.status && !opts.category && !opts.sentiment) {
|
|
17
20
|
throw errUsage('At least one of --status, --category, or --sentiment is required', 'Example: feedbackbasket feedback update <id> --status PLANNED');
|
|
18
21
|
}
|
|
22
|
+
if (opts.status === 'CLOSED' && !opts.closeReason) {
|
|
23
|
+
throw errUsage('--close-reason is required when status is CLOSED');
|
|
24
|
+
}
|
|
25
|
+
if (opts.closeReason && !validCloseReasons.has(opts.closeReason)) {
|
|
26
|
+
throw errUsage('Invalid close reason. Must be one of: DUPLICATE, NOT_PLANNED, COULD_NOT_REPRODUCE, NOT_ACTIONABLE, NO_LONGER_RELEVANT, SPAM, OTHER');
|
|
27
|
+
}
|
|
28
|
+
if (opts.closeReason === 'OTHER' && !opts.closeNote?.trim()) {
|
|
29
|
+
throw errUsage('--close-note is required when --close-reason is OTHER');
|
|
30
|
+
}
|
|
19
31
|
const client = requireClient();
|
|
20
32
|
const data = {};
|
|
21
33
|
if (opts.status)
|
|
@@ -24,6 +36,10 @@ export function createFeedbackUpdateCommand(getWriter) {
|
|
|
24
36
|
data['category'] = opts.category;
|
|
25
37
|
if (opts.sentiment)
|
|
26
38
|
data['sentiment'] = opts.sentiment;
|
|
39
|
+
if (opts.closeReason)
|
|
40
|
+
data['closeReason'] = opts.closeReason;
|
|
41
|
+
if (opts.closeNote)
|
|
42
|
+
data['closeNote'] = opts.closeNote;
|
|
27
43
|
const updated = await client.updateFeedback(id, data);
|
|
28
44
|
if (!writer.isMachineOutput()) {
|
|
29
45
|
console.log(brand.success(`Updated feedback ${id}`));
|
|
@@ -33,12 +49,20 @@ export function createFeedbackUpdateCommand(getWriter) {
|
|
|
33
49
|
console.log(` Category: ${opts.category}`);
|
|
34
50
|
if (opts.sentiment)
|
|
35
51
|
console.log(` Sentiment: ${opts.sentiment}`);
|
|
52
|
+
if (opts.closeReason)
|
|
53
|
+
console.log(` Reason: ${opts.closeReason}`);
|
|
36
54
|
}
|
|
37
55
|
writer.ok(updated, {
|
|
38
56
|
summary: `Updated feedback ${id}`,
|
|
39
57
|
breadcrumbs: [
|
|
40
|
-
{
|
|
41
|
-
|
|
58
|
+
{
|
|
59
|
+
action: 'View updated item',
|
|
60
|
+
cmd: `feedbackbasket feedback show ${id}`,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
action: 'Add a note',
|
|
64
|
+
cmd: `feedbackbasket feedback note ${id} "<note>"`,
|
|
65
|
+
},
|
|
42
66
|
{ action: 'Back to list', cmd: 'feedbackbasket feedback list' },
|
|
43
67
|
],
|
|
44
68
|
});
|
|
@@ -23,8 +23,7 @@ import { createFeedbackBulkUpdateCommand } from './feedback-bulk-update.js';
|
|
|
23
23
|
import { createFeedbackExportCommand } from './feedback-export.js';
|
|
24
24
|
import { createFeedbackReplyCommand, createFeedbackRepliesCommand } from './feedback-reply.js';
|
|
25
25
|
export function createFeedbackCommand(getWriter) {
|
|
26
|
-
const feedback = new Command('feedback')
|
|
27
|
-
.description('View and manage feedback');
|
|
26
|
+
const feedback = new Command('feedback').description('View and manage feedback');
|
|
28
27
|
// Write subcommands
|
|
29
28
|
feedback.addCommand(createFeedbackCreateCommand(getWriter));
|
|
30
29
|
feedback.addCommand(createFeedbackUpdateCommand(getWriter));
|
|
@@ -42,6 +41,7 @@ export function createFeedbackCommand(getWriter) {
|
|
|
42
41
|
.option('--all', 'Show feedback across all projects (ignore default project)')
|
|
43
42
|
.option('--category <category>', 'Filter by category (BUG, FEATURE_REQUEST, IMPROVEMENT, QUESTION)')
|
|
44
43
|
.option('--status <status>', 'Filter by status (OPEN, UNDER_REVIEW, PLANNED, IN_PROGRESS, COMPLETE, CLOSED)')
|
|
44
|
+
.option('--close-reason <reason>', 'Filter by close reason')
|
|
45
45
|
.option('--sentiment <sentiment>', 'Filter by sentiment (POSITIVE, NEGATIVE, NEUTRAL)')
|
|
46
46
|
.option('--search <query>', 'Search feedback content')
|
|
47
47
|
.option('--limit <n>', 'Max results (1-100)', '20')
|
|
@@ -54,6 +54,7 @@ export function createFeedbackCommand(getWriter) {
|
|
|
54
54
|
projectId: await resolveProjectId(client, opts.project, opts.all),
|
|
55
55
|
category: opts.category,
|
|
56
56
|
status: opts.status,
|
|
57
|
+
closeReason: opts.closeReason,
|
|
57
58
|
sentiment: opts.sentiment,
|
|
58
59
|
search: opts.search,
|
|
59
60
|
limit: parseInt(opts.limit, 10),
|
|
@@ -65,13 +66,22 @@ export function createFeedbackCommand(getWriter) {
|
|
|
65
66
|
}
|
|
66
67
|
const breadcrumbs = [];
|
|
67
68
|
if (opts.project) {
|
|
68
|
-
breadcrumbs.push({
|
|
69
|
+
breadcrumbs.push({
|
|
70
|
+
action: 'View bugs for this project',
|
|
71
|
+
cmd: `feedbackbasket bugs list --project ${opts.project}`,
|
|
72
|
+
});
|
|
69
73
|
}
|
|
70
74
|
if (result.pagination.hasMore) {
|
|
71
75
|
const nextOffset = parseInt(opts.offset, 10) + parseInt(opts.limit, 10);
|
|
72
|
-
breadcrumbs.push({
|
|
76
|
+
breadcrumbs.push({
|
|
77
|
+
action: 'Next page',
|
|
78
|
+
cmd: `feedbackbasket feedback list --offset ${nextOffset} --limit ${opts.limit}${opts.project ? ` --project ${opts.project}` : ''}`,
|
|
79
|
+
});
|
|
73
80
|
}
|
|
74
|
-
breadcrumbs.push({
|
|
81
|
+
breadcrumbs.push({
|
|
82
|
+
action: 'Search',
|
|
83
|
+
cmd: 'feedbackbasket feedback search "<query>"',
|
|
84
|
+
});
|
|
75
85
|
writer.ok(result.feedback, {
|
|
76
86
|
summary: `Showing ${result.feedback.length} of ${result.pagination.totalCount} feedback items`,
|
|
77
87
|
notice: result.pagination.hasMore ? `Use --offset ${parseInt(opts.offset, 10) + parseInt(opts.limit, 10)} to see more` : undefined,
|
|
@@ -92,8 +102,14 @@ export function createFeedbackCommand(getWriter) {
|
|
|
92
102
|
writer.ok(item, {
|
|
93
103
|
summary: `Feedback ${item.id}`,
|
|
94
104
|
breadcrumbs: [
|
|
95
|
-
{
|
|
96
|
-
|
|
105
|
+
{
|
|
106
|
+
action: 'Update status',
|
|
107
|
+
cmd: `feedbackbasket feedback update ${item.id} --status <STATUS>`,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
action: 'Add note',
|
|
111
|
+
cmd: `feedbackbasket feedback note ${item.id} "<note>"`,
|
|
112
|
+
},
|
|
97
113
|
{ action: 'Back to list', cmd: 'feedbackbasket feedback list' },
|
|
98
114
|
],
|
|
99
115
|
});
|
|
@@ -120,7 +136,10 @@ export function createFeedbackCommand(getWriter) {
|
|
|
120
136
|
summary: `${result.pagination.totalCount} result${result.pagination.totalCount === 1 ? '' : 's'} for "${query}"`,
|
|
121
137
|
breadcrumbs: [
|
|
122
138
|
{ action: 'List all feedback', cmd: 'feedbackbasket feedback list' },
|
|
123
|
-
{
|
|
139
|
+
{
|
|
140
|
+
action: 'Search bugs',
|
|
141
|
+
cmd: `feedbackbasket bugs list --search "${query}"`,
|
|
142
|
+
},
|
|
124
143
|
],
|
|
125
144
|
});
|
|
126
145
|
});
|
|
@@ -156,7 +175,7 @@ function renderFeedbackList(items) {
|
|
|
156
175
|
const cat = categoryEmoji[item.category ?? ''] ?? brand.muted('[?]');
|
|
157
176
|
const priority = priorityLabel(item.aiPriorityScore);
|
|
158
177
|
const content = item.content.length > 80 ? item.content.slice(0, 77) + '...' : item.content;
|
|
159
|
-
const status = brand.muted(`[${item.status}]`);
|
|
178
|
+
const status = brand.muted(`[${item.status}${item.closeReason ? ` · ${item.closeReason}` : ''}]`);
|
|
160
179
|
const proj = item.project ? brand.primary(item.project.name) : '';
|
|
161
180
|
console.log(`${cat} ${priority} ${status} ${proj} ${brand.muted(item.id)}`);
|
|
162
181
|
console.log(` ${content}`);
|
|
@@ -175,6 +194,8 @@ function renderFeedbackDetail(item) {
|
|
|
175
194
|
console.log();
|
|
176
195
|
const fields = [
|
|
177
196
|
['Status', item.status],
|
|
197
|
+
['Close Reason', item.closeReason],
|
|
198
|
+
['Close Note', item.closeNote],
|
|
178
199
|
['Category', item.category],
|
|
179
200
|
['Feedback Type', formatFeedbackType(item)],
|
|
180
201
|
['Sentiment', item.sentiment],
|
package/dist/src/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type FeedbackStatus = 'OPEN' | 'UNDER_REVIEW' | 'PLANNED' | 'IN_PROGRESS' | 'COMPLETE' | 'CLOSED';
|
|
2
|
+
export type FeedbackCloseReason = 'DUPLICATE' | 'NOT_PLANNED' | 'COULD_NOT_REPRODUCE' | 'NOT_ACTIONABLE' | 'NO_LONGER_RELEVANT' | 'SPAM' | 'OTHER';
|
|
2
3
|
export type FeedbackCategory = 'BUG' | 'FEATURE_REQUEST' | 'IMPROVEMENT' | 'QUESTION';
|
|
3
4
|
export type Sentiment = 'POSITIVE' | 'NEGATIVE' | 'NEUTRAL';
|
|
4
5
|
export type Severity = 'high' | 'medium' | 'low';
|
|
@@ -93,6 +94,9 @@ export interface Feedback {
|
|
|
93
94
|
content: string;
|
|
94
95
|
email?: string | null;
|
|
95
96
|
status: FeedbackStatus;
|
|
97
|
+
closeReason?: FeedbackCloseReason | null;
|
|
98
|
+
closeNote?: string | null;
|
|
99
|
+
closedAt?: string | null;
|
|
96
100
|
category?: FeedbackCategory | null;
|
|
97
101
|
sentiment?: Sentiment | null;
|
|
98
102
|
aiSummary?: string | null;
|
|
@@ -186,6 +190,8 @@ export interface FeedbackCreateInput {
|
|
|
186
190
|
type?: 'bug' | 'feature' | 'general';
|
|
187
191
|
category?: FeedbackCategory;
|
|
188
192
|
status?: FeedbackStatus;
|
|
193
|
+
closeReason?: FeedbackCloseReason;
|
|
194
|
+
closeNote?: string;
|
|
189
195
|
email?: string;
|
|
190
196
|
pageUrl?: string;
|
|
191
197
|
metadata?: Record<string, unknown>;
|
|
@@ -232,6 +238,7 @@ export interface FeedbackParams {
|
|
|
232
238
|
projectId?: string;
|
|
233
239
|
category?: FeedbackCategory;
|
|
234
240
|
status?: FeedbackStatus;
|
|
241
|
+
closeReason?: FeedbackCloseReason;
|
|
235
242
|
sentiment?: Sentiment;
|
|
236
243
|
search?: string;
|
|
237
244
|
limit?: number;
|
package/dist/src/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION: "3.
|
|
1
|
+
export declare const VERSION: "3.2.0";
|
|
2
2
|
export declare const USER_AGENT: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "feedbackbasket-cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Command-line interface for FeedbackBasket — manage feedback and waitlists from your terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/src/cli.js",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"chalk": "^5.3.0",
|
|
36
36
|
"commander": "^13.1.0",
|
|
37
|
-
"feedbackbasket-agent-contract": "3.
|
|
37
|
+
"feedbackbasket-agent-contract": "3.2.0",
|
|
38
38
|
"open": "^10.1.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
@@ -7,7 +7,7 @@ description: Manage FeedbackBasket projects, feedback, bugs, website widgets, mo
|
|
|
7
7
|
|
|
8
8
|
Full command-line interface for managing feedback, waitlist signups, bug reports, projects, widgets, and teams in FeedbackBasket. Works with any AI agent that can run shell commands.
|
|
9
9
|
|
|
10
|
-
The unified agent surface version is `3.
|
|
10
|
+
The unified agent surface version is `3.2.0`. It has 31 product operations. The CLI, stdio MCP package, and live Streamable HTTP MCP server implement the same contract.
|
|
11
11
|
|
|
12
12
|
## Authentication
|
|
13
13
|
|
|
@@ -39,7 +39,7 @@ Use the CLI when the agent has shell access and an existing CLI login. Use MCP w
|
|
|
39
39
|
|
|
40
40
|
For remote MCP, add `https://feedbackbasket.com/.well-known/mcp` to the host. Save it, select **Authenticate**, sign in, select an organization, select Read or Full access, select Selected projects or All projects, and select **Allow**. Browser OAuth is the recommended remote setup. Do not ask the user to paste an OAuth token.
|
|
41
41
|
|
|
42
|
-
For local STDIO MCP, CI, servers, or unattended automation, use `feedbackbasket-mcp-server@3.
|
|
42
|
+
For local STDIO MCP, CI, servers, or unattended automation, use `feedbackbasket-mcp-server@3.2.0` with an `fb_key_` credential from the host credential store or an environment variable. Browser OAuth is only for Streamable HTTP. STDIO still uses an environment credential. The CLI keeps `feedbackbasket login` and its private `fb_cli_` token flow in this release.
|
|
43
43
|
|
|
44
44
|
Access tokens, refresh tokens, CLI tokens, and MCP keys are private and are not interchangeable. Never put a credential in source, command arguments, logs, prompts, snapshots, generated files, or final responses. Use browser OAuth, the host credential store, or an environment variable as applicable.
|
|
45
45
|
|
|
@@ -135,6 +135,7 @@ Treat a supplied project key as production unless the user explicitly confirms a
|
|
|
135
135
|
# Read
|
|
136
136
|
feedbackbasket feedback list --project <id> --category BUG --status OPEN --sentiment NEGATIVE
|
|
137
137
|
feedbackbasket feedback list --search "login" --limit 50 --offset 0 --notes
|
|
138
|
+
feedbackbasket feedback list --status CLOSED --close-reason NOT_PLANNED
|
|
138
139
|
feedbackbasket feedback show <id>
|
|
139
140
|
feedbackbasket feedback search "crash on mobile" --project <id> --limit 10
|
|
140
141
|
|
|
@@ -146,7 +147,9 @@ feedbackbasket feedback note <id> "Investigating — appears related to auth flo
|
|
|
146
147
|
feedbackbasket feedback note update <id> <note-id> --content "Updated internal note"
|
|
147
148
|
feedbackbasket feedback note delete <id> <note-id> --yes
|
|
148
149
|
feedbackbasket feedback delete <id> --yes
|
|
149
|
-
feedbackbasket feedback
|
|
150
|
+
feedbackbasket feedback update <id> --status CLOSED --close-reason NOT_PLANNED
|
|
151
|
+
feedbackbasket feedback update <id> --status CLOSED --close-reason OTHER --close-note "Reason for closing"
|
|
152
|
+
feedbackbasket feedback bulk-update --status CLOSED --close-reason NOT_ACTIONABLE --ids id1,id2,id3 --yes
|
|
150
153
|
|
|
151
154
|
# Reply to submitter by email, widget/in-app thread, or both
|
|
152
155
|
feedbackbasket feedback reply <id> "Thanks for reporting — we pushed a fix!" --delivery email --reply-to support@example.com --yes
|
|
@@ -391,12 +394,13 @@ feedbackbasket feedback search "crash" --category BUG --agent
|
|
|
391
394
|
|
|
392
395
|
## Filtering Options
|
|
393
396
|
|
|
394
|
-
| Type
|
|
395
|
-
|
|
|
396
|
-
| Categories
|
|
397
|
-
| Statuses
|
|
398
|
-
|
|
|
399
|
-
|
|
|
397
|
+
| Type | Values |
|
|
398
|
+
| ------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
399
|
+
| Categories | `BUG`, `FEATURE_REQUEST`, `IMPROVEMENT`, `QUESTION` |
|
|
400
|
+
| Statuses | `OPEN`, `UNDER_REVIEW`, `PLANNED`, `IN_PROGRESS`, `COMPLETE`, `CLOSED` |
|
|
401
|
+
| Close reasons | `DUPLICATE`, `NOT_PLANNED`, `COULD_NOT_REPRODUCE`, `NOT_ACTIONABLE`, `NO_LONGER_RELEVANT`, `SPAM`, `OTHER` |
|
|
402
|
+
| Sentiments | `POSITIVE`, `NEGATIVE`, `NEUTRAL` |
|
|
403
|
+
| Bug Severity | `high`, `medium`, `low` |
|
|
400
404
|
|
|
401
405
|
## JSON Envelope
|
|
402
406
|
|
|
@@ -432,5 +436,6 @@ Errors include hints:
|
|
|
432
436
|
- Project names resolve case-insensitively with fuzzy matching
|
|
433
437
|
- Write operations use full scope (granted by default during login)
|
|
434
438
|
- Feedback IDs are stable CUIDs — safe to reference across commands
|
|
439
|
+
- Closing feedback requires `--close-reason`. The `OTHER` reason also requires `--close-note`.
|
|
435
440
|
- All timestamps are ISO 8601
|
|
436
441
|
- `--yes` confirms all high-impact CLI operations in agent or machine mode.
|