feedbackbasket-cli 0.3.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 +215 -0
- package/dist/bin/feedbackbasket.d.ts +2 -0
- package/dist/bin/feedbackbasket.js +3 -0
- package/dist/src/auth/login.d.ts +6 -0
- package/dist/src/auth/login.js +111 -0
- package/dist/src/auth/manager.d.ts +11 -0
- package/dist/src/auth/manager.js +38 -0
- package/dist/src/cli.d.ts +1 -0
- package/dist/src/cli.js +79 -0
- package/dist/src/client.d.ts +105 -0
- package/dist/src/client.js +133 -0
- package/dist/src/commands/auth.d.ts +5 -0
- package/dist/src/commands/auth.js +313 -0
- package/dist/src/commands/bugs.d.ts +3 -0
- package/dist/src/commands/bugs.js +120 -0
- package/dist/src/commands/doctor.d.ts +3 -0
- package/dist/src/commands/doctor.js +130 -0
- package/dist/src/commands/feedback-bulk-update.d.ts +3 -0
- package/dist/src/commands/feedback-bulk-update.js +39 -0
- package/dist/src/commands/feedback-delete.d.ts +3 -0
- package/dist/src/commands/feedback-delete.js +45 -0
- package/dist/src/commands/feedback-export.d.ts +3 -0
- package/dist/src/commands/feedback-export.js +43 -0
- package/dist/src/commands/feedback-note.d.ts +3 -0
- package/dist/src/commands/feedback-note.js +41 -0
- package/dist/src/commands/feedback-update.d.ts +3 -0
- package/dist/src/commands/feedback-update.js +54 -0
- package/dist/src/commands/feedback.d.ts +3 -0
- package/dist/src/commands/feedback.js +201 -0
- package/dist/src/commands/projects.d.ts +3 -0
- package/dist/src/commands/projects.js +218 -0
- package/dist/src/commands/setup.d.ts +3 -0
- package/dist/src/commands/setup.js +90 -0
- package/dist/src/commands/team.d.ts +3 -0
- package/dist/src/commands/team.js +112 -0
- package/dist/src/commands/widget.d.ts +3 -0
- package/dist/src/commands/widget.js +153 -0
- package/dist/src/config/config.d.ts +10 -0
- package/dist/src/config/config.js +41 -0
- package/dist/src/config/credentials.d.ts +12 -0
- package/dist/src/config/credentials.js +39 -0
- package/dist/src/output/codes.d.ts +16 -0
- package/dist/src/output/codes.js +29 -0
- package/dist/src/output/envelope.d.ts +24 -0
- package/dist/src/output/envelope.js +2 -0
- package/dist/src/output/errors.d.ts +13 -0
- package/dist/src/output/errors.js +37 -0
- package/dist/src/output/styled.d.ts +4 -0
- package/dist/src/output/styled.js +78 -0
- package/dist/src/output/theme.d.ts +25 -0
- package/dist/src/output/theme.js +43 -0
- package/dist/src/output/writer.d.ts +23 -0
- package/dist/src/output/writer.js +109 -0
- package/dist/src/prompt.d.ts +3 -0
- package/dist/src/prompt.js +32 -0
- package/dist/src/resolve.d.ts +9 -0
- package/dist/src/resolve.js +70 -0
- package/dist/src/types.d.ts +100 -0
- package/dist/src/types.js +2 -0
- package/dist/src/version.d.ts +2 -0
- package/dist/src/version.js +2 -0
- package/package.json +49 -0
- package/skills/feedbackbasket/SKILL.md +132 -0
package/README.md
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# FeedbackBasket CLI
|
|
2
|
+
|
|
3
|
+
Command-line interface for [FeedbackBasket](https://feedbackbasket.com) — manage feedback, bug reports, projects, and widgets from your terminal or through AI agents.
|
|
4
|
+
|
|
5
|
+
Inspired by the [Basecamp CLI](https://github.com/basecamp/basecamp-cli). Everything you can do in the dashboard, you can do from the CLI.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Install
|
|
11
|
+
npm install -g feedbackbasket-cli
|
|
12
|
+
|
|
13
|
+
# Authenticate (opens browser, full access by default)
|
|
14
|
+
feedbackbasket login
|
|
15
|
+
|
|
16
|
+
# Or use a token directly (for CI/headless)
|
|
17
|
+
feedbackbasket login --token fb_cli_your_token_here
|
|
18
|
+
|
|
19
|
+
# Start exploring
|
|
20
|
+
feedbackbasket projects list
|
|
21
|
+
feedbackbasket feedback list
|
|
22
|
+
feedbackbasket bugs list --severity high
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The first time you log in, a setup wizard walks you through selecting a default project and installing the Claude Code skill.
|
|
26
|
+
|
|
27
|
+
## Agent Usage
|
|
28
|
+
|
|
29
|
+
Any AI agent with shell access (Claude Code, Codex, Cursor, OpenCode) can use the CLI directly:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Agents should use --agent flag for raw JSON output
|
|
33
|
+
feedbackbasket projects list --agent
|
|
34
|
+
feedbackbasket feedback list --category BUG --agent
|
|
35
|
+
feedbackbasket feedback update <id> --status PLANNED --agent
|
|
36
|
+
feedbackbasket widget script myproject --agent
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Install Claude Code Skill
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
feedbackbasket setup claude
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Commands
|
|
46
|
+
|
|
47
|
+
### Authentication
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
feedbackbasket login # Browser OAuth flow (alias for auth login)
|
|
51
|
+
feedbackbasket login --token <token> # Manual token (CI/headless)
|
|
52
|
+
feedbackbasket logout # Clear credentials (alias for auth logout)
|
|
53
|
+
feedbackbasket auth status # Show auth state, scope, default project
|
|
54
|
+
feedbackbasket auth token # Print raw token (for scripting/piping)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Projects
|
|
58
|
+
|
|
59
|
+
All project commands accept **name or ID** (e.g. `feedbackbasket` or `cmn3c7sgv...`).
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
feedbackbasket projects list # List all projects with stats
|
|
63
|
+
feedbackbasket projects show <name-or-id> # Project details
|
|
64
|
+
feedbackbasket projects create "My App" --url https://... # Create project
|
|
65
|
+
feedbackbasket projects update myapp --name "New Name" # Update project
|
|
66
|
+
feedbackbasket projects delete myapp # Delete (with confirmation)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Feedback
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Read
|
|
73
|
+
feedbackbasket feedback list # List recent feedback
|
|
74
|
+
feedbackbasket feedback list --project myapp # Filter by project
|
|
75
|
+
feedbackbasket feedback list --category BUG # Filter by category
|
|
76
|
+
feedbackbasket feedback list --status OPEN # Filter by status
|
|
77
|
+
feedbackbasket feedback list --sentiment NEGATIVE # Filter by sentiment
|
|
78
|
+
feedbackbasket feedback list --search "login issue" # Text search
|
|
79
|
+
feedbackbasket feedback show <id> # View single item detail
|
|
80
|
+
feedbackbasket feedback search "crash on mobile" # Search shortcut
|
|
81
|
+
|
|
82
|
+
# Write
|
|
83
|
+
feedbackbasket feedback update <id> --status PLANNED # Update status
|
|
84
|
+
feedbackbasket feedback update <id> --category BUG # Update category
|
|
85
|
+
feedbackbasket feedback note <id> "Investigating this..." # Add internal note
|
|
86
|
+
feedbackbasket feedback delete <id> # Delete feedback
|
|
87
|
+
feedbackbasket feedback bulk-update --status CLOSED --ids id1,id2,id3
|
|
88
|
+
|
|
89
|
+
# Export
|
|
90
|
+
feedbackbasket feedback export myapp --format csv # Export to CSV
|
|
91
|
+
feedbackbasket feedback export myapp --format md # Export to Markdown
|
|
92
|
+
feedbackbasket feedback export myapp --format json # Export to JSON
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Bug Reports
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
feedbackbasket bugs list # All bugs
|
|
99
|
+
feedbackbasket bugs list --severity high # High severity only
|
|
100
|
+
feedbackbasket bugs list --status OPEN # Open bugs
|
|
101
|
+
feedbackbasket bugs stats # Bug statistics summary
|
|
102
|
+
feedbackbasket bugs stats --project myapp # Per-project stats
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Widget
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
# View current settings
|
|
109
|
+
feedbackbasket widget settings myapp
|
|
110
|
+
|
|
111
|
+
# Update widget configuration
|
|
112
|
+
feedbackbasket widget settings myapp --color "#22c55e" --label "Send Feedback"
|
|
113
|
+
feedbackbasket widget settings myapp --position bottom-left --display modal
|
|
114
|
+
feedbackbasket widget settings myapp --email-required --intro "How can we improve?"
|
|
115
|
+
|
|
116
|
+
# Get embed code (ready to paste into your HTML)
|
|
117
|
+
feedbackbasket widget script myapp
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Team
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
feedbackbasket team list # List organization members
|
|
124
|
+
feedbackbasket team role <memberId> --role admin # Update member role
|
|
125
|
+
feedbackbasket team remove <memberId> # Remove member
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Utilities
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
feedbackbasket doctor # Run diagnostics (auth, connectivity, integrations)
|
|
132
|
+
feedbackbasket setup claude # Install Claude Code skill
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Output Modes
|
|
136
|
+
|
|
137
|
+
The CLI automatically detects your environment:
|
|
138
|
+
|
|
139
|
+
| Context | Behavior |
|
|
140
|
+
|---------|----------|
|
|
141
|
+
| **Terminal (TTY)** | Styled, human-readable output with FeedbackBasket brand colors |
|
|
142
|
+
| **Piped** | JSON output automatically |
|
|
143
|
+
| `--json` | Full JSON envelope with breadcrumbs |
|
|
144
|
+
| `--quiet` / `--agent` | Raw JSON data only (no envelope) |
|
|
145
|
+
| `--md` | Markdown formatted |
|
|
146
|
+
|
|
147
|
+
### JSON Envelope
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"ok": true,
|
|
152
|
+
"data": [...],
|
|
153
|
+
"summary": "12 open bugs, 3 high severity",
|
|
154
|
+
"breadcrumbs": [
|
|
155
|
+
{ "action": "View high severity", "cmd": "feedbackbasket bugs list --severity high" }
|
|
156
|
+
]
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Breadcrumbs suggest the next logical command — useful for both humans and agents navigating without a full command catalog.
|
|
161
|
+
|
|
162
|
+
## Configuration
|
|
163
|
+
|
|
164
|
+
Credentials stored in `~/.config/feedbackbasket/credentials.json`. Config in `~/.config/feedbackbasket/config.json`.
|
|
165
|
+
|
|
166
|
+
### Default Project
|
|
167
|
+
|
|
168
|
+
Set during the login wizard, or manually:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
# Commands auto-scope to your default project
|
|
172
|
+
feedbackbasket feedback list # uses default project
|
|
173
|
+
feedbackbasket feedback list --project other-app # override
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Environment Variables
|
|
177
|
+
|
|
178
|
+
| Variable | Description |
|
|
179
|
+
|----------|-------------|
|
|
180
|
+
| `FEEDBACKBASKET_TOKEN` | CLI token (bypasses stored credentials) |
|
|
181
|
+
| `FEEDBACKBASKET_BASE_URL` | API base URL override |
|
|
182
|
+
|
|
183
|
+
### Global Flags
|
|
184
|
+
|
|
185
|
+
| Flag | Description |
|
|
186
|
+
|------|-------------|
|
|
187
|
+
| `--json` | Full JSON envelope output |
|
|
188
|
+
| `--quiet` / `--agent` | Raw JSON data only |
|
|
189
|
+
| `--md` | Markdown output |
|
|
190
|
+
| `--base-url <url>` | Override API base URL |
|
|
191
|
+
|
|
192
|
+
## Filter Options
|
|
193
|
+
|
|
194
|
+
| Type | Values |
|
|
195
|
+
|------|--------|
|
|
196
|
+
| **Categories** | `BUG`, `FEATURE_REQUEST`, `IMPROVEMENT`, `QUESTION` |
|
|
197
|
+
| **Statuses** | `OPEN`, `UNDER_REVIEW`, `PLANNED`, `IN_PROGRESS`, `COMPLETE`, `CLOSED` |
|
|
198
|
+
| **Sentiments** | `POSITIVE`, `NEGATIVE`, `NEUTRAL` |
|
|
199
|
+
| **Bug Severity** | `high`, `medium`, `low` |
|
|
200
|
+
|
|
201
|
+
## Development
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
git clone https://github.com/deifos/feedbackbasket-cli.git
|
|
205
|
+
cd feedbackbasket-cli
|
|
206
|
+
npm install
|
|
207
|
+
npm run dev -- --help # Run in development
|
|
208
|
+
npm run build # Build for production
|
|
209
|
+
npm run dev -- login # Test login flow
|
|
210
|
+
npm run dev -- doctor # Test diagnostics
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## License
|
|
214
|
+
|
|
215
|
+
MIT
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { URL } from 'node:url';
|
|
4
|
+
import open from 'open';
|
|
5
|
+
import { brand, logo } from '../output/theme.js';
|
|
6
|
+
const TIMEOUT_MS = 120_000;
|
|
7
|
+
const SUCCESS_HTML = `<!DOCTYPE html>
|
|
8
|
+
<html><head><title>FeedbackBasket CLI</title>
|
|
9
|
+
<style>
|
|
10
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
11
|
+
display: flex; align-items: center; justify-content: center; min-height: 100vh;
|
|
12
|
+
margin: 0; background: #0a0a0a; color: #fafafa; }
|
|
13
|
+
.card { text-align: center; max-width: 400px; padding: 3rem; }
|
|
14
|
+
h1 { color: #22c55e; font-size: 1.5rem; }
|
|
15
|
+
p { color: #a1a1aa; line-height: 1.6; }
|
|
16
|
+
</style></head>
|
|
17
|
+
<body><div class="card">
|
|
18
|
+
<h1>Authenticated!</h1>
|
|
19
|
+
<p>You can close this window and return to your terminal.</p>
|
|
20
|
+
</div></body></html>`;
|
|
21
|
+
const ERROR_HTML = (message) => `<!DOCTYPE html>
|
|
22
|
+
<html><head><title>FeedbackBasket CLI</title>
|
|
23
|
+
<style>
|
|
24
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
25
|
+
display: flex; align-items: center; justify-content: center; min-height: 100vh;
|
|
26
|
+
margin: 0; background: #0a0a0a; color: #fafafa; }
|
|
27
|
+
.card { text-align: center; max-width: 400px; padding: 3rem; }
|
|
28
|
+
h1 { color: #ef4444; font-size: 1.5rem; }
|
|
29
|
+
p { color: #a1a1aa; line-height: 1.6; }
|
|
30
|
+
</style></head>
|
|
31
|
+
<body><div class="card">
|
|
32
|
+
<h1>Authentication Failed</h1>
|
|
33
|
+
<p>${message}</p>
|
|
34
|
+
</div></body></html>`;
|
|
35
|
+
export async function browserLogin(baseUrl, scope = 'read') {
|
|
36
|
+
const state = randomUUID();
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
const server = createServer((req, res) => {
|
|
39
|
+
const url = new URL(req.url ?? '/', `http://127.0.0.1`);
|
|
40
|
+
if (url.pathname !== '/callback') {
|
|
41
|
+
res.writeHead(404);
|
|
42
|
+
res.end('Not found');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const receivedState = url.searchParams.get('state');
|
|
46
|
+
const token = url.searchParams.get('token');
|
|
47
|
+
const error = url.searchParams.get('error');
|
|
48
|
+
// CSRF validation
|
|
49
|
+
if (receivedState !== state) {
|
|
50
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
51
|
+
res.end(ERROR_HTML('State mismatch — possible CSRF attack. Please try again.'));
|
|
52
|
+
cleanup();
|
|
53
|
+
reject(new Error('State mismatch during authentication'));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
// User denied
|
|
57
|
+
if (error) {
|
|
58
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
59
|
+
res.end(ERROR_HTML(`Authorization was denied: ${error}`));
|
|
60
|
+
cleanup();
|
|
61
|
+
reject(new Error(`Authorization denied: ${error}`));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
// Missing token
|
|
65
|
+
if (!token) {
|
|
66
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
67
|
+
res.end(ERROR_HTML('No token received. Please try again.'));
|
|
68
|
+
cleanup();
|
|
69
|
+
reject(new Error('No token received in callback'));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// Success
|
|
73
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
74
|
+
res.end(SUCCESS_HTML);
|
|
75
|
+
cleanup();
|
|
76
|
+
resolve({ token, scope });
|
|
77
|
+
});
|
|
78
|
+
const timeout = setTimeout(() => {
|
|
79
|
+
cleanup();
|
|
80
|
+
reject(new Error('Authentication timed out after 2 minutes'));
|
|
81
|
+
}, TIMEOUT_MS);
|
|
82
|
+
function cleanup() {
|
|
83
|
+
clearTimeout(timeout);
|
|
84
|
+
server.close();
|
|
85
|
+
}
|
|
86
|
+
// Listen on random port on loopback
|
|
87
|
+
server.listen(0, '127.0.0.1', () => {
|
|
88
|
+
const addr = server.address();
|
|
89
|
+
if (!addr || typeof addr === 'string') {
|
|
90
|
+
cleanup();
|
|
91
|
+
reject(new Error('Failed to start local auth server'));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const port = addr.port;
|
|
95
|
+
const authorizeUrl = `${baseUrl}/cli/authorize?callback_port=${port}&state=${state}&scope=${scope}`;
|
|
96
|
+
console.log();
|
|
97
|
+
console.log(` ${logo()} CLI`);
|
|
98
|
+
console.log();
|
|
99
|
+
console.log(` ${brand.primary('Opening browser for authentication...')}`);
|
|
100
|
+
console.log();
|
|
101
|
+
console.log(` ${brand.muted('If the browser doesn\'t open, visit:')}`);
|
|
102
|
+
console.log(` ${brand.primary(authorizeUrl)}`);
|
|
103
|
+
console.log();
|
|
104
|
+
console.log(` ${brand.muted('Waiting for authentication...')}`);
|
|
105
|
+
console.log();
|
|
106
|
+
open(authorizeUrl).catch(() => {
|
|
107
|
+
// Browser open failed — user will use the URL manually
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type Credentials } from '../config/credentials.js';
|
|
2
|
+
export declare class AuthManager {
|
|
3
|
+
private credentials;
|
|
4
|
+
private loaded;
|
|
5
|
+
resolveToken(): string | null;
|
|
6
|
+
isAuthenticated(): boolean;
|
|
7
|
+
getCredentials(): Credentials | null;
|
|
8
|
+
getTokenPreview(): string | null;
|
|
9
|
+
getSource(): 'env' | 'file' | null;
|
|
10
|
+
logout(): void;
|
|
11
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { loadCredentials, clearCredentials, maskToken } from '../config/credentials.js';
|
|
2
|
+
export class AuthManager {
|
|
3
|
+
credentials = null;
|
|
4
|
+
loaded = false;
|
|
5
|
+
resolveToken() {
|
|
6
|
+
const creds = this.getCredentials();
|
|
7
|
+
return creds?.token ?? null;
|
|
8
|
+
}
|
|
9
|
+
isAuthenticated() {
|
|
10
|
+
return this.resolveToken() !== null;
|
|
11
|
+
}
|
|
12
|
+
getCredentials() {
|
|
13
|
+
if (!this.loaded) {
|
|
14
|
+
this.credentials = loadCredentials();
|
|
15
|
+
this.loaded = true;
|
|
16
|
+
}
|
|
17
|
+
return this.credentials;
|
|
18
|
+
}
|
|
19
|
+
getTokenPreview() {
|
|
20
|
+
const creds = this.getCredentials();
|
|
21
|
+
if (!creds)
|
|
22
|
+
return null;
|
|
23
|
+
return maskToken(creds.token);
|
|
24
|
+
}
|
|
25
|
+
getSource() {
|
|
26
|
+
if (process.env['FEEDBACKBASKET_TOKEN'])
|
|
27
|
+
return 'env';
|
|
28
|
+
const creds = this.getCredentials();
|
|
29
|
+
if (creds)
|
|
30
|
+
return 'file';
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
logout() {
|
|
34
|
+
clearCredentials();
|
|
35
|
+
this.credentials = null;
|
|
36
|
+
this.loaded = false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function run(): void;
|
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { VERSION } from './version.js';
|
|
3
|
+
import { OutputWriter, Format } from './output/writer.js';
|
|
4
|
+
import { CLIError } from './output/errors.js';
|
|
5
|
+
import { setBaseUrlOverride } from './config/config.js';
|
|
6
|
+
import { createAuthCommand, createLoginCommand, createLogoutCommand } from './commands/auth.js';
|
|
7
|
+
import { createProjectsCommand } from './commands/projects.js';
|
|
8
|
+
import { createFeedbackCommand } from './commands/feedback.js';
|
|
9
|
+
import { createBugsCommand } from './commands/bugs.js';
|
|
10
|
+
import { createDoctorCommand } from './commands/doctor.js';
|
|
11
|
+
import { createSetupCommand } from './commands/setup.js';
|
|
12
|
+
import { createWidgetCommand } from './commands/widget.js';
|
|
13
|
+
import { createTeamCommand } from './commands/team.js';
|
|
14
|
+
let writer;
|
|
15
|
+
function resolveFormat(opts) {
|
|
16
|
+
if (opts.agent || opts.quiet)
|
|
17
|
+
return Format.Quiet;
|
|
18
|
+
if (opts.json)
|
|
19
|
+
return Format.JSON;
|
|
20
|
+
if (opts.md)
|
|
21
|
+
return Format.Markdown;
|
|
22
|
+
return Format.Auto;
|
|
23
|
+
}
|
|
24
|
+
function getWriter() {
|
|
25
|
+
return writer;
|
|
26
|
+
}
|
|
27
|
+
export function run() {
|
|
28
|
+
const program = new Command('feedbackbasket')
|
|
29
|
+
.version(VERSION, '-v, --version')
|
|
30
|
+
.description('Command-line interface for FeedbackBasket')
|
|
31
|
+
.option('--json', 'Output as JSON envelope')
|
|
32
|
+
.option('--quiet', 'Output raw data only (no envelope)')
|
|
33
|
+
.option('--agent', 'Agent mode (alias for --quiet)')
|
|
34
|
+
.option('--md', 'Output as Markdown')
|
|
35
|
+
.option('--base-url <url>', 'API base URL override')
|
|
36
|
+
.hook('preAction', (thisCommand) => {
|
|
37
|
+
const opts = thisCommand.opts();
|
|
38
|
+
const format = resolveFormat(opts);
|
|
39
|
+
writer = new OutputWriter({ format });
|
|
40
|
+
if (opts.baseUrl) {
|
|
41
|
+
setBaseUrlOverride(opts.baseUrl);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
// Register commands
|
|
45
|
+
program.addCommand(createAuthCommand(getWriter));
|
|
46
|
+
program.addCommand(createLoginCommand(getWriter)); // alias: feedbackbasket login
|
|
47
|
+
program.addCommand(createLogoutCommand(getWriter)); // alias: feedbackbasket logout
|
|
48
|
+
program.addCommand(createProjectsCommand(getWriter));
|
|
49
|
+
program.addCommand(createFeedbackCommand(getWriter));
|
|
50
|
+
program.addCommand(createBugsCommand(getWriter));
|
|
51
|
+
program.addCommand(createWidgetCommand(getWriter));
|
|
52
|
+
program.addCommand(createTeamCommand(getWriter));
|
|
53
|
+
program.addCommand(createDoctorCommand(getWriter));
|
|
54
|
+
program.addCommand(createSetupCommand(getWriter));
|
|
55
|
+
// Global error handler
|
|
56
|
+
program.exitOverride();
|
|
57
|
+
(async () => {
|
|
58
|
+
try {
|
|
59
|
+
await program.parseAsync(process.argv);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (error instanceof CLIError) {
|
|
63
|
+
if (!writer) {
|
|
64
|
+
writer = new OutputWriter({ format: Format.Auto });
|
|
65
|
+
}
|
|
66
|
+
writer.err(error);
|
|
67
|
+
process.exit(error.exitCode);
|
|
68
|
+
}
|
|
69
|
+
// Commander's own errors (help, version, etc.)
|
|
70
|
+
if (error instanceof Error && 'exitCode' in error) {
|
|
71
|
+
const exitCode = error.exitCode;
|
|
72
|
+
process.exit(exitCode);
|
|
73
|
+
}
|
|
74
|
+
// Unknown errors
|
|
75
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
})();
|
|
79
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { ProjectsResponse, FeedbackResponse, BugReportsResponse, FeedbackParams, BugReportParams, UserProfile, Project, Feedback } from './types.js';
|
|
2
|
+
export declare class FeedbackBasketClient {
|
|
3
|
+
private http;
|
|
4
|
+
constructor(token: string, baseUrl: string);
|
|
5
|
+
me(): Promise<UserProfile>;
|
|
6
|
+
listProjects(): Promise<ProjectsResponse>;
|
|
7
|
+
getProject(id: string): Promise<Project>;
|
|
8
|
+
createProject(data: {
|
|
9
|
+
name: string;
|
|
10
|
+
url: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
}): Promise<{
|
|
13
|
+
project: Project;
|
|
14
|
+
}>;
|
|
15
|
+
updateProject(id: string, data: {
|
|
16
|
+
name?: string;
|
|
17
|
+
url?: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
}): Promise<Project>;
|
|
20
|
+
deleteProject(id: string): Promise<{
|
|
21
|
+
deleted: boolean;
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
}>;
|
|
25
|
+
getFeedback(params?: FeedbackParams): Promise<FeedbackResponse>;
|
|
26
|
+
getFeedbackById(id: string): Promise<Feedback>;
|
|
27
|
+
getBugReports(params?: BugReportParams): Promise<BugReportsResponse>;
|
|
28
|
+
getBugStats(params?: {
|
|
29
|
+
projectId?: string;
|
|
30
|
+
}): Promise<BugReportsResponse['stats']>;
|
|
31
|
+
searchFeedback(query: string, opts?: {
|
|
32
|
+
projectId?: string;
|
|
33
|
+
category?: string;
|
|
34
|
+
limit?: number;
|
|
35
|
+
}): Promise<FeedbackResponse>;
|
|
36
|
+
getWidgetSettings(projectId: string): Promise<{
|
|
37
|
+
projectId: string;
|
|
38
|
+
projectName: string;
|
|
39
|
+
settings: Record<string, unknown>;
|
|
40
|
+
}>;
|
|
41
|
+
updateWidgetSettings(projectId: string, settings: Record<string, unknown>): Promise<{
|
|
42
|
+
projectId: string;
|
|
43
|
+
projectName: string;
|
|
44
|
+
settings: Record<string, unknown>;
|
|
45
|
+
}>;
|
|
46
|
+
getWidgetScript(projectId: string): Promise<{
|
|
47
|
+
projectId: string;
|
|
48
|
+
projectName: string;
|
|
49
|
+
embedCode: string;
|
|
50
|
+
scriptUrl: string;
|
|
51
|
+
}>;
|
|
52
|
+
updateFeedback(id: string, data: {
|
|
53
|
+
status?: string;
|
|
54
|
+
category?: string;
|
|
55
|
+
sentiment?: string;
|
|
56
|
+
}): Promise<Feedback>;
|
|
57
|
+
createNote(feedbackId: string, content: string): Promise<{
|
|
58
|
+
id: string;
|
|
59
|
+
content: string;
|
|
60
|
+
createdAt: string;
|
|
61
|
+
}>;
|
|
62
|
+
deleteFeedback(id: string): Promise<{
|
|
63
|
+
deleted: boolean;
|
|
64
|
+
id: string;
|
|
65
|
+
}>;
|
|
66
|
+
bulkUpdateStatus(ids: string[], status: string): Promise<{
|
|
67
|
+
updated: number;
|
|
68
|
+
status: string;
|
|
69
|
+
}>;
|
|
70
|
+
updateNote(feedbackId: string, noteId: string, content: string): Promise<{
|
|
71
|
+
id: string;
|
|
72
|
+
content: string;
|
|
73
|
+
createdAt: string;
|
|
74
|
+
}>;
|
|
75
|
+
deleteNote(feedbackId: string, noteId: string): Promise<{
|
|
76
|
+
deleted: boolean;
|
|
77
|
+
id: string;
|
|
78
|
+
}>;
|
|
79
|
+
exportFeedback(projectId: string, format?: 'csv' | 'md' | 'json'): Promise<string>;
|
|
80
|
+
listTeam(): Promise<{
|
|
81
|
+
members: Array<{
|
|
82
|
+
memberId: string;
|
|
83
|
+
userId: string;
|
|
84
|
+
name: string;
|
|
85
|
+
email: string;
|
|
86
|
+
role: string;
|
|
87
|
+
joinedAt: string;
|
|
88
|
+
}>;
|
|
89
|
+
totalMembers: number;
|
|
90
|
+
}>;
|
|
91
|
+
updateMemberRole(memberId: string, role: string): Promise<{
|
|
92
|
+
memberId: string;
|
|
93
|
+
name: string;
|
|
94
|
+
email: string;
|
|
95
|
+
role: string;
|
|
96
|
+
}>;
|
|
97
|
+
removeMember(memberId: string): Promise<{
|
|
98
|
+
removed: boolean;
|
|
99
|
+
memberId: string;
|
|
100
|
+
name: string;
|
|
101
|
+
email: string;
|
|
102
|
+
}>;
|
|
103
|
+
private request;
|
|
104
|
+
private handleError;
|
|
105
|
+
}
|