feedbackbasket-cli 0.12.0 → 3.0.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 ADDED
@@ -0,0 +1,289 @@
1
+ # Changelog
2
+
3
+ ## Unreleased
4
+
5
+ ## [3.0.0] - 2026-08-22
6
+
7
+ ### Added
8
+
9
+ - Added all 31 versioned product-operation mappings, including note update and note deletion.
10
+ - Added contract checks for commands, HTTP paths, confirmations, documentation, and package release safety.
11
+
12
+ ### Changed
13
+
14
+ - High-impact agent commands now need `--yes`.
15
+ - The CLI version now matches the MCP and agent contract version.
16
+
17
+ ---
18
+
19
+ ## [0.12.0] - 2026-07-13
20
+
21
+ ### Added
22
+
23
+ - **Conversation controls** - enable or disable website visitor replies and native in-app follow-up replies independently from the CLI
24
+ - **Threaded conversation output** - `feedback replies` now shows both team messages and visitor follow-ups in chronological order
25
+
26
+ ### Changed
27
+
28
+ - **Mobile conversation guidance** - mobile status, breadcrumbs, README, and the packaged skill now cover SDK unread badges and same-thread replies
29
+ - **Agent thread safety** - agents preserve follow-up context on the original feedback item instead of creating duplicate feedback entries
30
+
31
+ ---
32
+
33
+ ## [0.11.0] - 2026-07-13
34
+
35
+ ### Added
36
+
37
+ - **Autonomous mobile setup** - `mobile setup`, `status`, `bundle-ids`, and `verify` let agents configure and confirm native or hosted mobile feedback without using the dashboard
38
+ - **Safe mobile lifecycle controls** - mobile integrations can be disabled or have their publishable project key rotated through explicit confirmation commands
39
+ - **Mobile setup guidance** - agent output includes Swift package details, hosted-form configuration, supported frameworks, and connection metadata
40
+ - **Native in-app replies** - the CLI recognizes mobile reply threads and supports `--delivery in-app` for responses displayed by the Swift SDK
41
+
42
+ ### Changed
43
+
44
+ - **Publishable key safety** - mobile project keys are masked by default and are returned only when `--include-publishable-key` is explicitly supplied
45
+ - **Safe mobile breadcrumbs** - generated follow-up commands fall back to resolved project IDs when a typed project reference contains shell-active characters
46
+ - **Agent skill coverage** - the packaged skill now detects mobile frameworks, performs CLI-backed setup, protects private CLI/API credentials, and verifies SDK heartbeats
47
+ - **Reply-channel guidance** - feedback details distinguish native in-app replies from website-widget replies so agents choose the correct delivery method
48
+
49
+ ---
50
+
51
+ ## [0.10.0] - 2026-07-10
52
+
53
+ ### Added
54
+
55
+ - **Waitlist capture mode** - `widget settings --capture-mode waitlist|feedback` switches the installed project script between waitlist and feedback capture
56
+ - **Waitlist management** - `waitlist list` supports search and pagination, while `waitlist export` provides the dashboard CSV from the terminal
57
+ - **Widget security settings** - configure automatic error tracking and visitor-approved console error sharing from the CLI
58
+
59
+ ### Changed
60
+
61
+ - **Inline widget trigger safety** - docs and packaged skill now explicitly tell agents to use `openFeedbackForm()` instead of internal widget methods such as `open()` or `openModal()`
62
+ - **Waitlist integration guidance** - `widget script`, README, and the packaged skill explain how to annotate existing forms for waitlist capture
63
+ - **Native HTTP client** - removed Axios and its transitive dependency tree in favor of Node's built-in `fetch`
64
+
65
+ ---
66
+
67
+ ## [0.9.3] - 2026-06-19
68
+
69
+ ### Changed
70
+
71
+ - **Agent issue filing guidance** - the packaged skill now explains how agents should file discovered issues into the right FeedbackBasket project with short descriptions and structured metadata
72
+
73
+ ---
74
+
75
+ ## [0.9.2] - 2026-06-12
76
+
77
+ ### Changed
78
+
79
+ - **Project URL safety** - `projects create` and `projects update --url` now require explicit confirmation before saving localhost URLs, and agent mode requires `--allow-local-url`
80
+ - **Packaged skill docs** - agents now ask for the real website URL instead of using localhost, and keep basic modal feedback as the default unless guided feedback is requested
81
+ - **Widget project selection** - agents now resolve the FeedbackBasket project for the current app instead of relying on the CLI default project when installing widgets
82
+ - **Widget guidance** - README, help text, and breadcrumbs no longer nudge users to enable guided feedback by default
83
+
84
+ ---
85
+
86
+ ## [0.9.1] - 2026-06-12
87
+
88
+ ### Changed
89
+
90
+ - **Inline widget trigger guidance** - `widget script` now shows custom button examples for inline widgets, including trigger-aware popup anchoring
91
+ - **Packaged skill docs** - agents now use `openFeedbackForm({ trigger: event.currentTarget })` when installing custom inline trigger buttons
92
+
93
+ ---
94
+
95
+ ## [0.9.0] - 2026-06-11
96
+
97
+ ### Added
98
+
99
+ - **Widget reply delivery** - `feedback reply` now supports `--delivery email|widget|both` so agents can reply in the widget thread, by email, or both
100
+ - **Widget thread visibility** - `feedback show` now includes whether feedback has an active widget thread
101
+
102
+ ### Changed
103
+
104
+ - **Agent reply safety guidance** - the packaged skill now requires agents to verify reply delivery method before responding
105
+
106
+ ---
107
+
108
+ ## [0.8.0] - 2026-06-11
109
+
110
+ ### Added
111
+
112
+ - **`feedbackbasket feedback create`** - create feedback from the terminal with project, type, category, status, email, page URL, metadata, and agent JSON output support
113
+
114
+ ### Changed
115
+
116
+ - **Agent reply safety guidance** - the packaged skill now tells agents to ask for a reply-to email before sending email replies when no project default is configured, and to use widget/dashboard replies for widget-only feedback
117
+
118
+ ---
119
+
120
+ ## [0.7.0] - 2026-06-10
121
+
122
+ ### Added
123
+
124
+ - **Prefilled email widget settings** - `widget settings` can now configure read-only prefilled emails and hide the email field only when a runtime email is provided
125
+ - **Feedback metadata output** - `feedback show` displays submitted widget metadata when available, and agent JSON output includes the metadata payload returned by the API
126
+
127
+ ---
128
+
129
+ ## [0.6.1] - 2026-06-08
130
+
131
+ ### Fixed
132
+
133
+ - **Attachment links in feedback output** — `feedback show` now displays submitted screenshot/image URLs, and feedback/bug JSON responses include attachment metadata for agents
134
+
135
+ ---
136
+
137
+ ## [0.6.0] - 2026-06-07
138
+
139
+ ### Added
140
+
141
+ - **`feedbackbasket widget flow`** — view, enable, reset, or apply JSON configs for guided feedback types and follow-up questions
142
+ - **Expanded widget settings flags** — configure button size, radius, icon, email visibility, attachments, branding, z-index, and guided mode from the CLI
143
+ - **Submitted details in feedback output** — `feedback show` now displays visitor-selected feedback type and follow-up answers
144
+
145
+ ### Changed
146
+
147
+ - **Agent skill docs** — updated widget setup guidance so agents can customize guided feedback flows during installation
148
+
149
+ ---
150
+
151
+ ## [0.5.0] - 2026-05-10
152
+
153
+ ### Added
154
+
155
+ - **`feedbackbasket login --manual`** — authenticate remote servers by approving in a browser and pasting the generated CLI token back into the terminal
156
+
157
+ ### Changed
158
+
159
+ - **Clearer manual-login guidance** — `--manual` is documented as the flow for servers that cannot receive the localhost browser callback, while still requiring outbound HTTPS
160
+ - **CLI token validation** — login now rejects MCP API keys (`fb_key_...`) with a clear hint because CLI login requires `fb_cli_...` tokens
161
+
162
+ ---
163
+
164
+ ## [0.4.0] - 2026-04-15
165
+
166
+ ### Added
167
+
168
+ - **`feedback reply <id> "<content>"`** — send an email reply directly to the feedback submitter. The customer receives a branded email with your reply and the original feedback, and their response goes to the configured reply-to address
169
+ - **`feedback replies <id>`** — list all replies sent for a feedback item (content, reply-to, who sent it, timestamp)
170
+ - **`--reply-to <email>` on `feedback reply`** — override the project's default reply-to for a single reply
171
+ - **`--reply-to <email>` on `projects update`** — set or clear the default reply-to email for a project (e.g. `projects update myapp --reply-to vlad@example.com`)
172
+ - **Interactive reply-to prompt** — when sending a reply with no `--reply-to` and no project default, the CLI asks the human whether to use their account email or enter a custom one (agent mode returns a clear error asking the agent to pass `--reply-to` or set a project default)
173
+ - **`replyToEmail` in project detail** — `projects show` now displays the configured reply-to address
174
+
175
+ ### API Endpoints
176
+
177
+ - `POST /api/v1/feedback/:id/replies` — send reply (requires `full` scope)
178
+ - `GET /api/v1/feedback/:id/replies` — list replies
179
+ - `PATCH /api/v1/projects/:id` — now accepts `replyToEmail` in the body
180
+
181
+ ---
182
+
183
+ ## [0.3.5] - 2026-03-27
184
+
185
+ ### Fixed
186
+
187
+ - **Name resolution for `--project` flag**: `feedback list --project prontoshoot` and `bugs list --project prontoshoot` now resolve names to IDs — previously returned empty results when using names
188
+
189
+ ### Changed
190
+
191
+ - **Project name in list output**: Bug list and feedback list now show the project name (in green) instead of just the ID — much easier to scan across projects
192
+
193
+ ---
194
+
195
+ ## [0.3.3] - 2026-03-27
196
+
197
+ ### Added
198
+
199
+ - **`--all` flag**: `feedback list --all` and `bugs list --all` to query across all projects, bypassing the default project filter
200
+
201
+ ---
202
+
203
+ ## [0.3.2] - 2026-03-27
204
+
205
+ ### Changed
206
+
207
+ - **Custom help output**: Basecamp-style grouped sections (CORE COMMANDS, SHORTCUTS, SEARCH & EXPORT, AUTH & CONFIG, FLAGS, EXAMPLES, LEARN MORE)
208
+ - **Short flags**: `-j` for `--json`, `-q` for `--quiet`, `-m` for `--md`
209
+ - **Branded header**: "FeedbackBasket CLI v0.3.2" with logo in help output
210
+
211
+ ---
212
+
213
+ ## [0.3.1] - 2026-03-27
214
+
215
+ ### Fixed
216
+
217
+ - **Login redirect flow**: Users who aren't logged in are now seamlessly redirected back to the CLI authorize page after login — no more needing to click the link twice
218
+
219
+ ### Added
220
+
221
+ - **Skills repo**: Standalone [feedbackbasket-skills](https://github.com/deifos/feedbackbasket-skills) repo with install.md and enhanced SKILL.md
222
+ - **CLI docs page**: New `/docs/cli` page on feedbackbasket.com with full command reference and terminal demo
223
+ - **Landing page updates**: CLI feature card, dark terminal mockup, "Agent-Ready CLI" positioning
224
+ - **Published to npm**: `npm install -g feedbackbasket-cli` now available
225
+
226
+ ---
227
+
228
+ ## [0.3.0] - 2026-03-26
229
+
230
+ ### Added
231
+
232
+ - **Feedback delete**: `feedback delete <id>` — permanently remove feedback items with confirmation prompt
233
+ - **Bulk status update**: `feedback bulk-update --status CLOSED --ids id1,id2,id3` — update multiple items at once
234
+ - **Feedback export**: `feedback export [project] --format csv|md|json` — export all feedback to CSV, Markdown, or JSON
235
+ - **Widget management**: `widget settings [project]` — view and update widget configuration (color, label, position, messages, etc.)
236
+ - **Widget embed code**: `widget script [project]` — get the embed script tag ready to paste into HTML
237
+ - **Team management**: `team list`, `team role <id> --role admin`, `team remove <id>` — manage organization members
238
+ - **Note edit/delete**: API support for editing and deleting feedback notes (via PATCH/DELETE endpoints)
239
+
240
+ ### Changed
241
+
242
+ - **Feedback commands expanded**: `feedback` now has 8 subcommands (list, show, search, update, note, delete, bulk-update, export)
243
+
244
+ ---
245
+
246
+ ## [0.2.0] - 2026-03-26
247
+
248
+ ### Added
249
+
250
+ - **Project CRUD**: `projects create`, `projects show`, `projects update`, `projects delete` — full project management from the CLI
251
+ - **Name resolution**: All project commands accept name or ID (e.g. `projects show feedbackbasket` instead of passing a cuid)
252
+ - **Fuzzy matching**: Typo suggestions ("Did you mean: feedbackbasket?") and ambiguity detection when multiple projects match
253
+ - **Top-level aliases**: `feedbackbasket login` and `feedbackbasket logout` (no need to type `auth login`)
254
+ - **Project alias**: `feedbackbasket project` works as alias for `feedbackbasket projects`
255
+ - **Onboarding wizard**: Multi-step first-login flow — authenticate, select default project, install Claude Code skill
256
+ - **Default project**: Set during onboarding or via config — auto-applied to `feedback list`, `bugs list`, etc. (override with `--project`)
257
+
258
+ ### Changed
259
+
260
+ - **Full access by default**: Auth login now defaults to `--scope full` (like Basecamp), no more read-only by default
261
+ - **Brand colors**: All terminal output uses FeedbackBasket green (#22c55e) accent instead of generic cyan
262
+ - **Centralized theme**: New `src/output/theme.ts` with brand colors, semantic helpers, and logo renderer
263
+ - **Doctor command**: Now shows branded "FeedbackBasket CLI Diagnostics" header with logo
264
+ - **Login flow**: Shows branded "FeedbackBasket CLI" header with green URL during browser auth
265
+ - **Better error messages**: Auth and project fetch errors now show the actual error message instead of silent failures
266
+ - **Delete confirmation**: `projects delete` prompts for confirmation in interactive mode (skipped with `--yes` or `--agent`)
267
+
268
+ ---
269
+
270
+ ## [0.1.0] - 2026-03-25
271
+
272
+ ### Added
273
+
274
+ - Initial release of the FeedbackBasket CLI
275
+ - **Authentication**: Browser-based OAuth flow (`auth login`) and manual token input (`auth login --token`)
276
+ - **Auth management**: `auth status`, `auth logout`, `auth token` commands
277
+ - **Projects**: `projects list` — view all accessible projects with feedback stats
278
+ - **Feedback**: `feedback list` with filtering (--project, --category, --status, --sentiment, --search, --limit, --offset, --notes)
279
+ - **Feedback detail**: `feedback show <id>` — view single feedback item with full details
280
+ - **Feedback search**: `feedback search <query>` — cross-project text search
281
+ - **Feedback write**: `feedback update <id>` — update status, category, sentiment (requires full scope)
282
+ - **Feedback notes**: `feedback note <id> "<content>"` — add internal notes (requires full scope)
283
+ - **Bug reports**: `bugs list` with severity/status filtering, `bugs stats` for summary
284
+ - **Diagnostics**: `doctor` command — health checks for auth, connectivity, and integrations
285
+ - **Agent integration**: `setup claude` — install SKILL.md for Claude Code
286
+ - **Output modes**: Auto-detect (styled TTY / JSON piped), `--json` (envelope with breadcrumbs), `--quiet`/`--agent` (raw data), `--md` (Markdown)
287
+ - **JSON envelope**: Basecamp-inspired `{ ok, data, summary, breadcrumbs }` response format
288
+ - **Structured errors**: Error codes, exit codes, and recovery hints
289
+ - **Credential storage**: `~/.config/feedbackbasket/credentials.json` with `FEEDBACKBASKET_TOKEN` env var support
package/README.md CHANGED
@@ -26,7 +26,9 @@ feedbackbasket feedback create "Login button is broken" --project myapp --type b
26
26
  feedbackbasket bugs list --severity high
27
27
  ```
28
28
 
29
- The first time you log in, a setup wizard walks you through selecting a default project and installing the Claude Code skill.
29
+ The first time you log in, a setup wizard walks you through selecting a default project and installing the Claude Code skill.
30
+
31
+ The CLI uses agent surface version `3.0.0`. CLI login accepts only private CLI credentials. It does not accept MCP keys. Use `--yes` for high-impact commands in agent or machine mode. Interactive use can show a confirmation prompt.
30
32
 
31
33
  ## Agent Usage
32
34
 
@@ -50,7 +52,47 @@ When installing or configuring a widget for the current app, agents should not r
50
52
  feedbackbasket setup claude
51
53
  ```
52
54
 
53
- ## Commands
55
+ <!-- BEGIN GENERATED AGENT CAPABILITIES -->
56
+ ## Agent capability contract
57
+
58
+ Agent surface version: `3.0.0`. The CLI and both MCP transports implement the same 31 product operations.
59
+
60
+ | Product operation | CLI command | MCP tool | Required access | Confirm |
61
+ | --- | --- | --- | --- | --- |
62
+ | `projects.list` | `projects list` | `list_projects` | `read:projects` | No |
63
+ | `projects.get` | `projects show` | `get_project` | `read:projects; allowed project` | No |
64
+ | `projects.create` | `projects create` | `create_project` | `write:projects; unrestricted key` | No |
65
+ | `projects.update` | `projects update` | `update_project` | `write:projects; allowed project` | No |
66
+ | `projects.delete` | `projects delete` | `delete_project` | `write:projects; allowed project` | Yes |
67
+ | `feedback.list` | `feedback list` | `get_feedback` | `read:feedback; allowed project` | No |
68
+ | `feedback.get` | `feedback show` | `get_feedback_item` | `read:feedback; allowed project` | No |
69
+ | `feedback.search` | `feedback search` | `search_feedback` | `read:feedback; allowed project` | No |
70
+ | `feedback.create` | `feedback create` | `create_feedback` | `write:feedback; allowed project` | No |
71
+ | `feedback.update` | `feedback update` | `update_feedback` | `write:feedback; allowed project` | No |
72
+ | `feedback.delete` | `feedback delete` | `delete_feedback` | `write:feedback; allowed project` | Yes |
73
+ | `feedback.bulkUpdate` | `feedback bulk-update` | `bulk_update_feedback` | `write:feedback; allowed project` | Yes |
74
+ | `feedback.export` | `feedback export` | `export_feedback` | `read:feedback; allowed project` | No |
75
+ | `bugs.list` | `bugs list` | `get_bug_reports` | `read:feedback; allowed project` | No |
76
+ | `bugs.stats` | `bugs stats` | `get_bug_stats` | `read:feedback; allowed project` | No |
77
+ | `notes.create` | `feedback note` | `create_feedback_note` | `write:notes; allowed project` | No |
78
+ | `notes.update` | `feedback note update` | `update_feedback_note` | `write:notes; allowed project` | No |
79
+ | `notes.delete` | `feedback note delete` | `delete_feedback_note` | `write:notes; allowed project` | Yes |
80
+ | `replies.list` | `feedback replies` | `list_feedback_replies` | `read:feedback; allowed project` | No |
81
+ | `replies.send` | `feedback reply` | `send_feedback_reply` | `write:replies; allowed project` | Yes |
82
+ | `widget.getSettings` | `widget settings` | `get_widget_settings` | `read:projects; allowed project` | No |
83
+ | `widget.updateSettings` | `widget update`<br>`widget flow` | `update_widget_settings` | `write:widget; allowed project` | No |
84
+ | `widget.getScript` | `widget script` | `get_widget_script` | `read:projects; allowed project` | No |
85
+ | `mobile.get` | `mobile status`<br>`mobile verify` | `get_mobile_integration` | `read:projects; allowed project` | No |
86
+ | `mobile.update` | `mobile setup`<br>`mobile bundle`<br>`mobile conversations`<br>`mobile disable` | `update_mobile_integration` | `write:mobile; allowed project` | No |
87
+ | `mobile.rotateKey` | `mobile rotate-key` | `rotate_mobile_project_key` | `write:mobile; allowed project` | Yes |
88
+ | `waitlist.list` | `waitlist list` | `get_waitlist` | `read:feedback; allowed project` | No |
89
+ | `waitlist.export` | `waitlist export` | `export_waitlist` | `read:feedback; allowed project` | No |
90
+ | `team.list` | `team list` | `list_team_members` | `write:team; unrestricted key` | No |
91
+ | `team.updateRole` | `team role` | `update_team_member_role` | `write:team; unrestricted key` | Yes |
92
+ | `team.remove` | `team remove` | `remove_team_member` | `write:team; unrestricted key` | Yes |
93
+ <!-- END GENERATED AGENT CAPABILITIES -->
94
+
95
+ ## Commands
54
96
 
55
97
  ### Authentication
56
98
 
@@ -0,0 +1,38 @@
1
+ import { type ProductOperationId } from 'feedbackbasket-agent-contract';
2
+ export declare const CLI_CAPABILITIES: readonly {
3
+ operationId: ProductOperationId;
4
+ commands: readonly string[];
5
+ }[];
6
+ export declare const CLI_EXEMPTIONS: ({
7
+ readonly id: "auth";
8
+ readonly surface: "cli";
9
+ readonly reason: "Authentication is local CLI credential management, not a product operation.";
10
+ } | {
11
+ readonly id: "login";
12
+ readonly surface: "cli";
13
+ readonly reason: "The login shortcut starts the local CLI authentication flow.";
14
+ } | {
15
+ readonly id: "logout";
16
+ readonly surface: "cli";
17
+ readonly reason: "Logout removes a local CLI credential.";
18
+ } | {
19
+ readonly id: "doctor";
20
+ readonly surface: "cli";
21
+ readonly reason: "Doctor checks local CLI configuration and connectivity.";
22
+ } | {
23
+ readonly id: "setup";
24
+ readonly surface: "cli";
25
+ readonly reason: "Setup installs local agent guidance and does not change FeedbackBasket product data.";
26
+ } | {
27
+ readonly id: "output";
28
+ readonly surface: "cli";
29
+ readonly reason: "Output flags change terminal formatting only.";
30
+ } | {
31
+ readonly id: "initialize";
32
+ readonly surface: "mcp";
33
+ readonly reason: "MCP initialization is a transport protocol operation.";
34
+ } | {
35
+ readonly id: "resources";
36
+ readonly surface: "mcp";
37
+ readonly reason: "Public MCP resources describe the service and do not access customer product data.";
38
+ })[];
@@ -0,0 +1,6 @@
1
+ import { PARITY_EXEMPTIONS, PRODUCT_OPERATIONS, } from 'feedbackbasket-agent-contract';
2
+ export const CLI_CAPABILITIES = PRODUCT_OPERATIONS.map((operation) => ({
3
+ operationId: operation.id,
4
+ commands: operation.cli.commands,
5
+ }));
6
+ export const CLI_EXEMPTIONS = PARITY_EXEMPTIONS.filter(({ surface }) => surface === 'cli');
package/dist/src/cli.d.ts CHANGED
@@ -1 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createProgram(): Command;
1
3
  export declare function run(): void;
package/dist/src/cli.js CHANGED
@@ -27,7 +27,7 @@ function resolveFormat(opts) {
27
27
  function getWriter() {
28
28
  return writer;
29
29
  }
30
- export function run() {
30
+ export function createProgram() {
31
31
  const program = new Command('feedbackbasket')
32
32
  .version(VERSION, '-v, --version')
33
33
  .description('Command-line interface for FeedbackBasket')
@@ -71,6 +71,10 @@ export function run() {
71
71
  program.addCommand(createSetupCommand(getWriter));
72
72
  // Global error handler
73
73
  program.exitOverride();
74
+ return program;
75
+ }
76
+ export function run() {
77
+ const program = createProgram();
74
78
  (async () => {
75
79
  try {
76
80
  await program.parseAsync(process.argv);
@@ -1,5 +1,6 @@
1
1
  import { Command } from 'commander';
2
2
  import type { OutputWriter } from '../output/writer.js';
3
3
  export declare function createAuthCommand(getWriter: () => OutputWriter): Command;
4
+ export declare function resolveAuthScope(value: string): 'read' | 'full';
4
5
  export declare function createLoginCommand(getWriter: () => OutputWriter): Command;
5
6
  export declare function createLogoutCommand(getWriter: () => OutputWriter): Command;
@@ -23,7 +23,7 @@ export function createAuthCommand(getWriter) {
23
23
  .action(async (opts) => {
24
24
  const writer = getWriter();
25
25
  const config = loadConfig();
26
- const scope = opts.scope === 'read' ? 'read' : 'full';
26
+ const scope = resolveAuthScope(opts.scope);
27
27
  const isInteractive = !writer.isMachineOutput() && process.stdin.isTTY;
28
28
  // ── Step 1: Authentication ──
29
29
  if (isInteractive) {
@@ -294,6 +294,11 @@ export function createAuthCommand(getWriter) {
294
294
  });
295
295
  return auth;
296
296
  }
297
+ export function resolveAuthScope(value) {
298
+ if (value === 'read' || value === 'full')
299
+ return value;
300
+ throw errUsage('Scope must be "read" or "full"');
301
+ }
297
302
  // Top-level aliases: `feedbackbasket login` and `feedbackbasket logout`
298
303
  export function createLoginCommand(getWriter) {
299
304
  return new Command('login')
@@ -4,11 +4,13 @@ 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
+ import { requireHighImpactConfirmation } from '../confirmation.js';
7
8
  export function createFeedbackBulkUpdateCommand(getWriter) {
8
9
  return new Command('bulk-update')
9
10
  .description('Update status for multiple feedback items at once')
10
11
  .requiredOption('--status <status>', 'New status (OPEN, UNDER_REVIEW, PLANNED, IN_PROGRESS, COMPLETE, CLOSED)')
11
12
  .requiredOption('--ids <ids>', 'Comma-separated feedback IDs')
13
+ .option('--yes', 'Confirm the bulk update')
12
14
  .action(async (opts) => {
13
15
  const writer = getWriter();
14
16
  const client = requireClient();
@@ -16,6 +18,7 @@ export function createFeedbackBulkUpdateCommand(getWriter) {
16
18
  if (ids.length === 0) {
17
19
  throw errUsage('At least one ID is required', 'Example: --ids id1,id2,id3');
18
20
  }
21
+ 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.');
19
22
  const result = await client.bulkUpdateStatus(ids, opts.status);
20
23
  if (!writer.isMachineOutput()) {
21
24
  console.log(` ${brand.success('✓')} Updated ${result.updated} feedback items to ${brand.bold(result.status)}`);
@@ -4,7 +4,7 @@ import { AuthManager } from '../auth/manager.js';
4
4
  import { loadConfig } from '../config/config.js';
5
5
  import { errAuth } from '../output/errors.js';
6
6
  import { brand } from '../output/theme.js';
7
- import { confirm } from '../prompt.js';
7
+ import { requireHighImpactConfirmation } from '../confirmation.js';
8
8
  export function createFeedbackDeleteCommand(getWriter) {
9
9
  return new Command('delete')
10
10
  .argument('<id>', 'Feedback ID to delete')
@@ -16,12 +16,8 @@ export function createFeedbackDeleteCommand(getWriter) {
16
16
  if (!opts.yes && !writer.isMachineOutput() && process.stdin.isTTY) {
17
17
  console.log(` ${brand.warning('Warning:')} This will permanently delete feedback ${brand.bold(id)}`);
18
18
  console.log();
19
- const confirmed = await confirm(' Delete this feedback?', false);
20
- if (!confirmed) {
21
- console.log(brand.muted(' Cancelled.'));
22
- return;
23
- }
24
19
  }
20
+ await requireHighImpactConfirmation(writer, Boolean(opts.yes), 'Delete this feedback?', '--yes is required to delete feedback in machine mode.');
25
21
  const result = await client.deleteFeedback(id);
26
22
  if (!writer.isMachineOutput()) {
27
23
  console.log(` ${brand.success('✓')} Deleted feedback ${id}`);
@@ -4,15 +4,19 @@ 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
+ import { requireHighImpactConfirmation } from '../confirmation.js';
7
8
  export function createFeedbackNoteCommand(getWriter) {
8
- return new Command('note')
9
- .argument('<id>', 'Feedback ID to add a note to')
9
+ const note = new Command('note')
10
+ .argument('[id]', 'Feedback ID to add a note to')
10
11
  .argument('[content]', 'Note content (or use --content)')
11
12
  .description('Add an internal note to a feedback item')
12
13
  .option('--content <text>', 'Note content (alternative to positional argument)')
13
14
  .action(async (id, contentArg, opts) => {
14
15
  const writer = getWriter();
15
16
  const content = contentArg ?? opts.content;
17
+ if (!id) {
18
+ throw errUsage('Feedback ID is required', 'Example: feedbackbasket feedback note <id> "Your note here"');
19
+ }
16
20
  if (!content) {
17
21
  throw errUsage('Note content is required', 'Example: feedbackbasket feedback note <id> "Your note here"');
18
22
  }
@@ -30,6 +34,34 @@ export function createFeedbackNoteCommand(getWriter) {
30
34
  ],
31
35
  });
32
36
  });
37
+ note
38
+ .command('update <feedbackId> <noteId>')
39
+ .description('Update an internal feedback note')
40
+ .requiredOption('--content <text>', 'New note content')
41
+ .action(async (feedbackId, noteId, opts) => {
42
+ const writer = getWriter();
43
+ const client = requireClient();
44
+ const result = await client.updateNote(feedbackId, noteId, opts.content);
45
+ writer.ok(result, {
46
+ summary: `Updated note ${noteId}`,
47
+ breadcrumbs: [{ action: 'View feedback', cmd: `feedbackbasket feedback show ${feedbackId}` }],
48
+ });
49
+ });
50
+ note
51
+ .command('delete <feedbackId> <noteId>')
52
+ .description('Delete an internal feedback note')
53
+ .option('--yes', 'Confirm note deletion')
54
+ .action(async (feedbackId, noteId, opts) => {
55
+ const writer = getWriter();
56
+ const client = requireClient();
57
+ await requireHighImpactConfirmation(writer, Boolean(opts.yes), `Delete note ${noteId}?`, '--yes is required to delete a note in machine mode.');
58
+ const result = await client.deleteNote(feedbackId, noteId);
59
+ writer.ok(result, {
60
+ summary: `Deleted note ${noteId}`,
61
+ breadcrumbs: [{ action: 'View feedback', cmd: `feedbackbasket feedback show ${feedbackId}` }],
62
+ });
63
+ });
64
+ return note;
33
65
  }
34
66
  function requireClient() {
35
67
  const manager = new AuthManager();
@@ -5,6 +5,7 @@ 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 { ask } from '../prompt.js';
8
+ import { requireHighImpactConfirmation } from '../confirmation.js';
8
9
  const deliveryOptions = new Set(['email', 'widget', 'in-app', 'both']);
9
10
  export function createFeedbackReplyCommand(getWriter) {
10
11
  return new Command('reply')
@@ -14,6 +15,7 @@ export function createFeedbackReplyCommand(getWriter) {
14
15
  .option('--content <text>', 'Reply content (alternative to positional argument)')
15
16
  .option('--delivery <delivery>', 'Reply delivery (email, widget, in-app, both)', 'email')
16
17
  .option('--reply-to <email>', 'Reply-to email for email delivery')
18
+ .option('--yes', 'Confirm that the reply can be sent')
17
19
  .action(async (id, contentArg, opts) => {
18
20
  const writer = getWriter();
19
21
  const content = contentArg ?? opts.content;
@@ -61,6 +63,7 @@ export function createFeedbackReplyCommand(getWriter) {
61
63
  if (sendsWidget && !feedback.hasWidgetAccess) {
62
64
  throw errUsage('This feedback has no in-app or widget reply thread.', 'Use --delivery email for feedback with an email address, or ask the human how they want to respond.');
63
65
  }
66
+ await requireHighImpactConfirmation(writer, Boolean(opts.yes), `Send this reply by ${delivery}?`, '--yes is required to send a reply in machine mode.');
64
67
  const result = await client.sendReply(id, content, {
65
68
  replyToEmail: replyTo,
66
69
  destinations,
@@ -67,6 +67,7 @@ export function createMobileCommand(getWriter) {
67
67
  });
68
68
  mobile
69
69
  .command('bundle-ids [project]')
70
+ .alias('bundle')
70
71
  .description('Add or remove allowed iOS bundle IDs')
71
72
  .option('--add <bundle-id>', 'Bundle ID to add (repeatable)', collect, [])
72
73
  .option('--remove <bundle-id>', 'Bundle ID to remove (repeatable)', collect, [])
@@ -5,6 +5,7 @@ import { loadConfig } from '../config/config.js';
5
5
  import { errAuth, errUsage } from '../output/errors.js';
6
6
  import { brand, divider } from '../output/theme.js';
7
7
  import { confirm } from '../prompt.js';
8
+ import { requireHighImpactConfirmation } from '../confirmation.js';
8
9
  import { resolveProject } from '../resolve.js';
9
10
  export function createProjectsCommand(getWriter) {
10
11
  const projects = new Command('projects')
@@ -139,17 +140,12 @@ export function createProjectsCommand(getWriter) {
139
140
  const resolved = await resolveProject(client, idOrName);
140
141
  const id = resolved.id;
141
142
  const projectName = resolved.name;
142
- // Confirmation (skip in agent mode or --yes)
143
143
  if (!opts.yes && !writer.isMachineOutput() && process.stdin.isTTY) {
144
144
  console.log(` ${brand.warning('Warning:')} This will permanently delete project "${brand.bold(projectName)}"`);
145
145
  console.log(` ${brand.muted('All feedback, notes, and settings will be lost.')}`);
146
146
  console.log();
147
- const confirmed = await confirm(` Delete "${projectName}"?`, false);
148
- if (!confirmed) {
149
- console.log(brand.muted(' Cancelled.'));
150
- return;
151
- }
152
147
  }
148
+ await requireHighImpactConfirmation(writer, Boolean(opts.yes), `Delete "${projectName}"?`, '--yes is required to delete a project in machine mode.');
153
149
  const result = await client.deleteProject(id);
154
150
  if (!writer.isMachineOutput()) {
155
151
  console.log(` ${brand.success('✓')} Deleted project "${brand.bold(result.name)}"`);
@@ -4,7 +4,7 @@ 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, divider } from '../output/theme.js';
7
- import { confirm } from '../prompt.js';
7
+ import { requireHighImpactConfirmation } from '../confirmation.js';
8
8
  export function createTeamCommand(getWriter) {
9
9
  const team = new Command('team')
10
10
  .description('Manage organization members');
@@ -31,12 +31,14 @@ export function createTeamCommand(getWriter) {
31
31
  .command('role <memberId>')
32
32
  .description('Update a member\'s role')
33
33
  .requiredOption('--role <role>', 'New role: admin or member')
34
+ .option('--yes', 'Confirm the role change')
34
35
  .action(async (memberId, opts) => {
35
36
  const writer = getWriter();
36
37
  const client = requireClient();
37
38
  if (!['admin', 'member'].includes(opts.role)) {
38
39
  throw errUsage('Role must be "admin" or "member"');
39
40
  }
41
+ await requireHighImpactConfirmation(writer, Boolean(opts.yes), `Change member ${memberId} to ${opts.role}?`, '--yes is required to change a team role in machine mode.');
40
42
  const result = await client.updateMemberRole(memberId, opts.role);
41
43
  if (!writer.isMachineOutput()) {
42
44
  console.log(` ${brand.success('✓')} Updated ${brand.bold(result.name)} to ${brand.bold(result.role)}`);
@@ -57,13 +59,7 @@ export function createTeamCommand(getWriter) {
57
59
  .action(async (memberId, opts) => {
58
60
  const writer = getWriter();
59
61
  const client = requireClient();
60
- if (!opts.yes && !writer.isMachineOutput() && process.stdin.isTTY) {
61
- const confirmed = await confirm(` Remove member ${memberId}?`, false);
62
- if (!confirmed) {
63
- console.log(brand.muted(' Cancelled.'));
64
- return;
65
- }
66
- }
62
+ await requireHighImpactConfirmation(writer, Boolean(opts.yes), `Remove member ${memberId}?`, '--yes is required to remove a team member in machine mode.');
67
63
  const result = await client.removeMember(memberId);
68
64
  if (!writer.isMachineOutput()) {
69
65
  console.log(` ${brand.success('✓')} Removed ${brand.bold(result.name)} (${result.email})`);
@@ -50,6 +50,7 @@ export function createWidgetCommand(getWriter) {
50
50
  // --- widget settings ---
51
51
  widget
52
52
  .command('settings [project]')
53
+ .alias('update')
53
54
  .description('View or update widget settings')
54
55
  .option('--capture-mode <mode>', 'Capture mode (feedback, waitlist)')
55
56
  .option('--color <hex>', 'Button color (e.g. #22c55e)')
@@ -0,0 +1,2 @@
1
+ import type { OutputWriter } from './output/writer.js';
2
+ export declare function requireHighImpactConfirmation(writer: OutputWriter, confirmedByFlag: boolean, question: string, hint: string): Promise<void>;
@@ -0,0 +1,11 @@
1
+ import { errUsage } from './output/errors.js';
2
+ import { confirm } from './prompt.js';
3
+ export async function requireHighImpactConfirmation(writer, confirmedByFlag, question, hint) {
4
+ if (confirmedByFlag)
5
+ return;
6
+ if (writer.isMachineOutput() || !process.stdin.isTTY) {
7
+ throw errUsage(hint, `${hint} Re-run the command with --yes.`);
8
+ }
9
+ if (!(await confirm(` ${question}`, false)))
10
+ throw errUsage('Action cancelled');
11
+ }
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.12.0";
2
- export declare const USER_AGENT = "FeedbackBasket-CLI/0.12.0";
1
+ export declare const VERSION: "3.0.0";
2
+ export declare const USER_AGENT: string;
@@ -1,2 +1,3 @@
1
- export const VERSION = '0.12.0';
1
+ import { AGENT_SURFACE_VERSION } from 'feedbackbasket-agent-contract';
2
+ export const VERSION = AGENT_SURFACE_VERSION;
2
3
  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.12.0",
3
+ "version": "3.0.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",
@@ -12,7 +12,10 @@
12
12
  "test": "tsx --test tests/*.test.ts",
13
13
  "dev": "tsx bin/feedbackbasket.ts",
14
14
  "start": "node dist/bin/feedbackbasket.js",
15
- "prepublishOnly": "npm run build"
15
+ "check:parity": "tsx scripts/check-parity.ts",
16
+ "docs:generate": "tsx scripts/generate-capabilities.ts",
17
+ "docs:check": "tsx scripts/generate-capabilities.ts --check",
18
+ "prepublishOnly": "npm run check:parity && npm run docs:check && npm test && npm run build"
16
19
  },
17
20
  "keywords": [
18
21
  "feedbackbasket",
@@ -31,6 +34,7 @@
31
34
  "dependencies": {
32
35
  "chalk": "^5.3.0",
33
36
  "commander": "^13.1.0",
37
+ "feedbackbasket-agent-contract": "3.0.0",
34
38
  "open": "^10.1.0"
35
39
  },
36
40
  "devDependencies": {
@@ -44,6 +48,7 @@
44
48
  "files": [
45
49
  "dist/**/*",
46
50
  "skills/**/*",
47
- "README.md"
51
+ "README.md",
52
+ "CHANGELOG.md"
48
53
  ]
49
54
  }
@@ -5,7 +5,9 @@ description: Manage FeedbackBasket projects, feedback, bugs, website widgets, mo
5
5
 
6
6
  # FeedbackBasket CLI
7
7
 
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.
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
+
10
+ The unified agent surface version is `3.0.0`. It has 31 product operations. The CLI, stdio MCP package, and live Streamable HTTP MCP server implement the same contract.
9
11
 
10
12
  ## Authentication
11
13
 
@@ -27,7 +29,17 @@ feedbackbasket doctor # Full diagnostics
27
29
  | `--quiet` | Raw JSON data only | Scripting |
28
30
  | `--md` | Markdown | Documentation |
29
31
 
30
- **Agent rule**: Always use `--agent` for programmatic access. Parse the JSON output directly.
32
+ **Agent rule**: Always use `--agent` for programmatic access. Parse the JSON output directly.
33
+
34
+ ## MCP Workflow Selection
35
+
36
+ Use the CLI when the agent has shell access and an existing CLI login. Use MCP when the host supports MCP tools. For MCP, use either the `feedbackbasket-mcp-server@3.0.0` stdio package or the direct Streamable HTTP endpoint at `https://feedbackbasket.com/.well-known/mcp`.
37
+
38
+ CLI credentials 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 the host credential store or an environment variable.
39
+
40
+ MCP read keys can use read operations only. Full keys can use writes that their scopes permit. A project-restricted key can access only its allowed projects. Project creation and team operations need an unrestricted full key. If a write is denied, do not try a different security path. Ask the user for the required access.
41
+
42
+ High-impact operations need explicit approval. MCP calls must include `confirm: true`. CLI agent or machine commands must include `--yes`. These rules apply to project deletion, feedback deletion, bulk feedback updates, note deletion, replies, mobile key rotation, team role changes, and team removal.
31
43
 
32
44
  ## Quick Reference
33
45
 
@@ -121,16 +133,18 @@ feedbackbasket feedback search "crash on mobile" --project <id> --limit 10
121
133
  # Write
122
134
  feedbackbasket feedback create "Login button is broken" --content "Clicking Log in does nothing in Safari." --project <id> --type bug
123
135
  feedbackbasket feedback create "Feature idea" --content "Let users export saved views." --project <id> --type feature --metadata source=agent
124
- feedbackbasket feedback update <id> --status PLANNED --category BUG --sentiment NEGATIVE
125
- feedbackbasket feedback note <id> "Investigating — appears related to auth flow"
126
- feedbackbasket feedback delete <id> --yes
127
- feedbackbasket feedback bulk-update --status CLOSED --ids id1,id2,id3
136
+ feedbackbasket feedback update <id> --status PLANNED --category BUG --sentiment NEGATIVE
137
+ feedbackbasket feedback note <id> "Investigating — appears related to auth flow"
138
+ feedbackbasket feedback note update <id> <note-id> --content "Updated internal note"
139
+ feedbackbasket feedback note delete <id> <note-id> --yes
140
+ feedbackbasket feedback delete <id> --yes
141
+ feedbackbasket feedback bulk-update --status CLOSED --ids id1,id2,id3 --yes
128
142
 
129
143
  # Reply to submitter by email, widget/in-app thread, or both
130
- feedbackbasket feedback reply <id> "Thanks for reporting — we pushed a fix!" --delivery email --reply-to support@example.com
131
- feedbackbasket feedback reply <id> "<content>" --delivery widget
132
- feedbackbasket feedback reply <id> "<content>" --delivery in-app
133
- feedbackbasket feedback reply <id> "<content>" --delivery both --reply-to support@example.com
144
+ feedbackbasket feedback reply <id> "Thanks for reporting — we pushed a fix!" --delivery email --reply-to support@example.com --yes
145
+ feedbackbasket feedback reply <id> "<content>" --delivery widget --yes
146
+ feedbackbasket feedback reply <id> "<content>" --delivery in-app --yes
147
+ feedbackbasket feedback reply <id> "<content>" --delivery both --reply-to support@example.com --yes
134
148
  feedbackbasket feedback replies <id> # show the complete conversation
135
149
 
136
150
  # Export
@@ -182,7 +196,9 @@ Waitlist mode keeps the same project script and binds to the host app's own anno
182
196
  </form>
183
197
  ```
184
198
 
185
- Email is required and name is optional. Use `data-feedbackbasket-state="loading|success|error"` for custom UI, or listen for the bubbling `feedbackbasket:waitlist:success` and `feedbackbasket:waitlist:error` events. Do not add a competing submit handler.
199
+ The form must be served from the website origin saved on the FeedbackBasket project. Email is required and name is optional. The script binds forms already on the page and forms added later, uses native browser validation, disables submit controls during the request, and keeps the host app's styling.
200
+
201
+ Use `data-feedbackbasket-state="loading|success|error"` for custom UI. The bubbling `feedbackbasket:waitlist:success` event includes `detail.email` and `detail.duplicate`; `feedbackbasket:waitlist:error` includes `detail.message` and `detail.status`. Do not add a competing submit handler. Repeat submissions for the same project and email update the existing signup rather than creating a duplicate.
186
202
 
187
203
  ### Waitlist Signups
188
204
 
@@ -224,7 +240,7 @@ Use the basic widget experience by default: `displayMode` stays `modal`, and gui
224
240
  ### Team
225
241
  ```bash
226
242
  feedbackbasket team list
227
- feedbackbasket team role <memberId> --role admin
243
+ feedbackbasket team role <memberId> --role admin --yes
228
244
  feedbackbasket team remove <memberId> --yes
229
245
  ```
230
246
 
@@ -316,10 +332,10 @@ feedbackbasket feedback show <id> --agent
316
332
  ```bash
317
333
  # Agent reads context, asks which delivery method to use, then sends it
318
334
  feedbackbasket feedback show <id> --agent # read email, replyChannel, project.replyToEmail
319
- feedbackbasket feedback reply <id> "<drafted response>" --delivery widget --agent
320
- feedbackbasket feedback reply <id> "<drafted response>" --delivery in-app --agent
321
- feedbackbasket feedback reply <id> "<drafted response>" --delivery email --reply-to support@example.com --agent
322
- feedbackbasket feedback reply <id> "<drafted response>" --delivery both --reply-to support@example.com --agent
335
+ feedbackbasket feedback reply <id> "<drafted response>" --delivery widget --yes --agent
336
+ feedbackbasket feedback reply <id> "<drafted response>" --delivery in-app --yes --agent
337
+ feedbackbasket feedback reply <id> "<drafted response>" --delivery email --reply-to support@example.com --yes --agent
338
+ feedbackbasket feedback reply <id> "<drafted response>" --delivery both --reply-to support@example.com --yes --agent
323
339
  feedbackbasket feedback update <id> --status COMPLETE --agent
324
340
  feedbackbasket feedback note <id> "Replied via CLI" --agent
325
341
  ```
@@ -381,10 +397,10 @@ Errors include hints:
381
397
  ## Invariants
382
398
 
383
399
  - Always authenticate before data commands
384
- - `--agent` flag suppresses all interactive prompts and confirmations
400
+ - `--agent` suppresses interactive prompts. High-impact operations still need `--yes`.
385
401
  - Default project (set during login) is used when `--project` is not specified
386
402
  - Project names resolve case-insensitively with fuzzy matching
387
403
  - Write operations use full scope (granted by default during login)
388
404
  - Feedback IDs are stable CUIDs — safe to reference across commands
389
405
  - All timestamps are ISO 8601
390
- - `--yes` flag skips delete confirmations in interactive mode
406
+ - `--yes` confirms all high-impact CLI operations in agent or machine mode