@zoobbe/cli 1.2.0 → 1.2.1
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 +205 -19
- package/package.json +1 -1
- package/src/commands/activity.js +102 -0
- package/src/commands/analytics.js +281 -0
- package/src/commands/api-key.js +140 -0
- package/src/commands/auth.js +2 -0
- package/src/commands/automation.js +255 -0
- package/src/commands/board.js +295 -0
- package/src/commands/card.js +367 -0
- package/src/commands/checklist.js +173 -0
- package/src/commands/import.js +101 -0
- package/src/commands/list.js +213 -0
- package/src/commands/notification.js +92 -0
- package/src/commands/page.js +253 -0
- package/src/commands/timer.js +234 -0
- package/src/commands/webhook.js +141 -0
- package/src/commands/workspace.js +174 -0
- package/src/index.js +10 -0
- package/src/lib/client.js +42 -2
- package/src/utils/format.js +39 -0
- package/src/utils/prompts.js +40 -0
- package/src/utils/resolve.js +67 -0
package/README.md
CHANGED
|
@@ -64,6 +64,7 @@ zoobbe auth login --url https://api.your-instance.com
|
|
|
64
64
|
zoobbe auth whoami # Show current user
|
|
65
65
|
zoobbe auth status # Show auth details
|
|
66
66
|
zoobbe auth logout # Clear credentials
|
|
67
|
+
zoobbe auth clear # Reset apiUrl and webUrl
|
|
67
68
|
```
|
|
68
69
|
|
|
69
70
|
## Commands
|
|
@@ -72,9 +73,18 @@ zoobbe auth logout # Clear credentials
|
|
|
72
73
|
|
|
73
74
|
```bash
|
|
74
75
|
zoobbe workspace list # List your workspaces
|
|
75
|
-
zoobbe workspace switch <
|
|
76
|
-
zoobbe workspace
|
|
76
|
+
zoobbe workspace switch <name> # Set active workspace
|
|
77
|
+
zoobbe workspace create <name> # Create a new workspace
|
|
78
|
+
zoobbe workspace info # Show active workspace details
|
|
77
79
|
zoobbe workspace members # List workspace members
|
|
80
|
+
zoobbe workspace update --name <n> # Rename active workspace
|
|
81
|
+
zoobbe workspace delete # Delete active workspace
|
|
82
|
+
zoobbe workspace invite -e <email> # Invite a user by email
|
|
83
|
+
zoobbe workspace join-link # Generate a join link
|
|
84
|
+
zoobbe workspace join-link --delete
|
|
85
|
+
zoobbe workspace add-member -u <id>
|
|
86
|
+
zoobbe workspace remove-member -u <id>
|
|
87
|
+
zoobbe workspace member-role -u <id> --role admin
|
|
78
88
|
```
|
|
79
89
|
|
|
80
90
|
### Boards
|
|
@@ -83,24 +93,98 @@ zoobbe workspace members # List workspace members
|
|
|
83
93
|
zoobbe board list # List boards in active workspace
|
|
84
94
|
zoobbe board list --all # Include archived boards
|
|
85
95
|
zoobbe board create <name> # Create a new board
|
|
86
|
-
zoobbe board create <name> -v Public
|
|
96
|
+
zoobbe board create <name> -v Public
|
|
87
97
|
zoobbe board info <id> # Show board details
|
|
88
98
|
zoobbe board open <id> # Open board in browser
|
|
99
|
+
zoobbe board update <id> -t <title> -d <desc>
|
|
89
100
|
zoobbe board archive <id> # Archive a board
|
|
101
|
+
zoobbe board restore <id> # Restore an archived board
|
|
102
|
+
zoobbe board delete <id> # Permanently delete a board
|
|
103
|
+
zoobbe board members <id> # List board members
|
|
104
|
+
zoobbe board add-member <id> -u <userId> --role admin
|
|
105
|
+
zoobbe board remove-member <id> -u <userId>
|
|
106
|
+
zoobbe board invite <id> -e <email>
|
|
107
|
+
zoobbe board join-link <id> # Generate join link
|
|
108
|
+
zoobbe board labels <id> # List board labels
|
|
109
|
+
zoobbe board label-create <id> --text "Bug" --color red
|
|
110
|
+
zoobbe board label-delete <id> --label <labelId>
|
|
111
|
+
zoobbe board visibility <id> Public
|
|
112
|
+
zoobbe board activities <id> # Show board activity log
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Lists
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
zoobbe list ls -b <boardId> # List all lists on a board
|
|
119
|
+
zoobbe list create <title> -b <boardId>
|
|
120
|
+
zoobbe list update <listId> -t <title> --color "#ff0000"
|
|
121
|
+
zoobbe list delete <listId> # Delete list and all cards
|
|
122
|
+
zoobbe list archive <listId> # Archive a list
|
|
123
|
+
zoobbe list archive --unarchive # Unarchive a list
|
|
124
|
+
zoobbe list archive-cards <listId> # Archive all cards in a list
|
|
125
|
+
zoobbe list copy <listId> # Copy list with all cards
|
|
126
|
+
zoobbe list watch <listId> # Toggle watch
|
|
127
|
+
zoobbe list archived -b <boardId> # Show archived lists
|
|
90
128
|
```
|
|
91
129
|
|
|
92
130
|
### Cards
|
|
93
131
|
|
|
94
132
|
```bash
|
|
95
133
|
zoobbe card list --board <id> # List cards on a board
|
|
96
|
-
zoobbe card create <title> -b <
|
|
97
|
-
zoobbe card create <title> -b <id> -l <
|
|
98
|
-
zoobbe card
|
|
99
|
-
zoobbe card
|
|
100
|
-
zoobbe card move <
|
|
101
|
-
zoobbe card
|
|
102
|
-
zoobbe card
|
|
103
|
-
zoobbe card
|
|
134
|
+
zoobbe card create <title> -b <id> # Create card in first list
|
|
135
|
+
zoobbe card create <title> -b <id> -l <listId> --due 2026-04-01 --priority high
|
|
136
|
+
zoobbe card info <cardId> # Show card details
|
|
137
|
+
zoobbe card update <cardId> -t <title> -d <desc>
|
|
138
|
+
zoobbe card move <cardId> -l <listName>
|
|
139
|
+
zoobbe card copy <cardId> -l <listId>
|
|
140
|
+
zoobbe card assign <cardId> -u <userId>
|
|
141
|
+
zoobbe card unassign <cardId> -u <userId>
|
|
142
|
+
zoobbe card members <cardId> # List card members
|
|
143
|
+
zoobbe card comment <cardId> "message"
|
|
144
|
+
zoobbe card comments <cardId> # List comments
|
|
145
|
+
zoobbe card label <cardId> # Add/remove labels (interactive)
|
|
146
|
+
zoobbe card done <cardId> # Mark as complete
|
|
147
|
+
zoobbe card undone <cardId> # Mark as incomplete
|
|
148
|
+
zoobbe card archive <cardId>
|
|
149
|
+
zoobbe card delete <cardId> # Permanently delete
|
|
150
|
+
zoobbe card due <cardId> 2026-05-01
|
|
151
|
+
zoobbe card due <cardId> --remove
|
|
152
|
+
zoobbe card priority <cardId> high
|
|
153
|
+
zoobbe card watch <cardId>
|
|
154
|
+
zoobbe card attachments <cardId> # List attachments
|
|
155
|
+
zoobbe card attach <cardId> ./file.pdf
|
|
156
|
+
zoobbe card activities <cardId> # Show activity log
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Checklists
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
zoobbe checklist list <cardId> # List checklists
|
|
163
|
+
zoobbe checklist add <cardId> "QA Tasks" # Add a checklist
|
|
164
|
+
zoobbe checklist rename <cardId> <clId> -t "New" # Rename
|
|
165
|
+
zoobbe checklist delete <cardId> <clId> # Delete checklist
|
|
166
|
+
zoobbe checklist add-item <cardId> <clId> "Test login flow"
|
|
167
|
+
zoobbe checklist check <cardId> <clId> <itemId> # Toggle complete
|
|
168
|
+
zoobbe checklist update-item <cardId> <clId> <itemId> -t "Updated"
|
|
169
|
+
zoobbe checklist delete-item <cardId> <clId> <itemId>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Timers
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
zoobbe timer start <cardId> # Start stopwatch
|
|
176
|
+
zoobbe timer start <cardId> --mode pomodoro
|
|
177
|
+
zoobbe timer pause <cardId>
|
|
178
|
+
zoobbe timer resume <cardId>
|
|
179
|
+
zoobbe timer stop <cardId> # Stop and save session
|
|
180
|
+
zoobbe timer reset <cardId>
|
|
181
|
+
zoobbe timer mode <cardId> pomodoro # Switch mode
|
|
182
|
+
zoobbe timer get <cardId> # Get current state
|
|
183
|
+
zoobbe timer sessions <cardId> # List sessions
|
|
184
|
+
zoobbe timer status # Overall timer status
|
|
185
|
+
zoobbe timer running # All running timers
|
|
186
|
+
zoobbe timer stats # Timer statistics
|
|
187
|
+
zoobbe timer delete <cardId> # Delete timer data
|
|
104
188
|
```
|
|
105
189
|
|
|
106
190
|
### Pages
|
|
@@ -108,8 +192,99 @@ zoobbe card done <card-id> # Mark card as complete
|
|
|
108
192
|
```bash
|
|
109
193
|
zoobbe page list # List pages
|
|
110
194
|
zoobbe page create <title> # Create a new page
|
|
111
|
-
zoobbe page info <
|
|
112
|
-
zoobbe page open <
|
|
195
|
+
zoobbe page info <pageId> # Show page details
|
|
196
|
+
zoobbe page open <pageId> # Open page in browser
|
|
197
|
+
zoobbe page update <pageId> -t <title> --icon "📝"
|
|
198
|
+
zoobbe page delete <pageId> # Soft delete
|
|
199
|
+
zoobbe page delete <pageId> --permanent
|
|
200
|
+
zoobbe page archive <pageId>
|
|
201
|
+
zoobbe page restore <pageId>
|
|
202
|
+
zoobbe page duplicate <pageId>
|
|
203
|
+
zoobbe page favorite <pageId> # Toggle favorite
|
|
204
|
+
zoobbe page share <pageId> --public
|
|
205
|
+
zoobbe page share <pageId> --revoke
|
|
206
|
+
zoobbe page share <pageId> -u <userId> --role editor
|
|
207
|
+
zoobbe page members <pageId>
|
|
208
|
+
zoobbe page add-member <pageId> -u <userId> --role editor
|
|
209
|
+
zoobbe page remove-member <pageId> -u <userId>
|
|
210
|
+
zoobbe page comments <pageId> # List comments
|
|
211
|
+
zoobbe page comment <pageId> "Great doc!"
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Notifications
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
zoobbe notification list # List notifications
|
|
218
|
+
zoobbe notification list --unread # Only unread
|
|
219
|
+
zoobbe notification count # Unread count
|
|
220
|
+
zoobbe notification read <id> # Mark as read
|
|
221
|
+
zoobbe notification read-all # Mark all as read
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Activity
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
zoobbe activity board <boardId> # Board activity log
|
|
228
|
+
zoobbe activity card <cardId> # Card activity log
|
|
229
|
+
zoobbe activity me # Your recent activity
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Analytics
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
zoobbe analytics board <boardId> # Board overview
|
|
236
|
+
zoobbe analytics metrics <boardId> --range 30d
|
|
237
|
+
zoobbe analytics time <boardId> # Time analytics
|
|
238
|
+
zoobbe analytics timeline <boardId> # Full timeline
|
|
239
|
+
zoobbe analytics productivity <boardId> # User productivity
|
|
240
|
+
zoobbe analytics workflow <boardId> # Workflow analytics
|
|
241
|
+
zoobbe analytics workspace # Workspace analytics
|
|
242
|
+
zoobbe analytics user # Your personal analytics
|
|
243
|
+
zoobbe analytics trends # Trends
|
|
244
|
+
zoobbe analytics export <boardId> -o report.json
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Automations
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
zoobbe automation list -b <boardId>
|
|
251
|
+
zoobbe automation create -b <boardId> # Interactive creation
|
|
252
|
+
zoobbe automation update <id> -b <boardId> --name "New name"
|
|
253
|
+
zoobbe automation delete <id> -b <boardId>
|
|
254
|
+
zoobbe automation toggle <id> -b <boardId> # Enable/disable
|
|
255
|
+
zoobbe automation logs <id> -b <boardId> # Execution logs
|
|
256
|
+
zoobbe automation options -b <boardId> # Available triggers/actions
|
|
257
|
+
zoobbe automation info <id> -b <boardId>
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Webhooks
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
zoobbe webhook list # List webhooks
|
|
264
|
+
zoobbe webhook create --url <url> --events card.created,card.moved --name "My Hook"
|
|
265
|
+
zoobbe webhook update <id> --url <newUrl>
|
|
266
|
+
zoobbe webhook delete <id>
|
|
267
|
+
zoobbe webhook logs <id> # Delivery logs
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### API Keys
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
zoobbe api-key list # List your API keys
|
|
274
|
+
zoobbe api-key create --name "CI" # Create a new key
|
|
275
|
+
zoobbe api-key delete <id>
|
|
276
|
+
zoobbe api-key rename <id> --name "Production"
|
|
277
|
+
zoobbe api-key regenerate <id> # Regenerate key
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
> Note: Some API key operations may require session auth (browser login) rather than API key auth.
|
|
281
|
+
|
|
282
|
+
### Import
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
zoobbe import trello --key <trelloKey> --token <trelloToken>
|
|
286
|
+
zoobbe import status <jobId> # Check import progress
|
|
287
|
+
zoobbe import jobs # List all import jobs
|
|
113
288
|
```
|
|
114
289
|
|
|
115
290
|
### Search
|
|
@@ -167,16 +342,27 @@ zoobbe board list --format table # Table (default)
|
|
|
167
342
|
|
|
168
343
|
Most commands have short aliases:
|
|
169
344
|
|
|
170
|
-
| Command
|
|
171
|
-
|
|
172
|
-
| `
|
|
173
|
-
| `
|
|
174
|
-
| `
|
|
175
|
-
| `list`
|
|
345
|
+
| Command | Alias |
|
|
346
|
+
|----------------|--------|
|
|
347
|
+
| `workspace` | `ws` |
|
|
348
|
+
| `board` | `b` |
|
|
349
|
+
| `card` | `c` |
|
|
350
|
+
| `list` | `l` |
|
|
351
|
+
| `checklist` | `cl` |
|
|
352
|
+
| `page` | `p` |
|
|
353
|
+
| `timer` | `t` |
|
|
354
|
+
| `notification` | `n` |
|
|
355
|
+
| `analytics` | `an` |
|
|
356
|
+
| `automation` | `auto` |
|
|
357
|
+
| `webhook` | `wh` |
|
|
358
|
+
| `api-key` | `ak` |
|
|
176
359
|
|
|
177
360
|
```bash
|
|
178
361
|
zoobbe b ls # Same as: zoobbe board list
|
|
179
362
|
zoobbe c ls -b <id> # Same as: zoobbe card list --board <id>
|
|
363
|
+
zoobbe cl ls <cardId> # Same as: zoobbe checklist list <cardId>
|
|
364
|
+
zoobbe t start <cardId> # Same as: zoobbe timer start <cardId>
|
|
365
|
+
zoobbe n ls --unread # Same as: zoobbe notification list --unread
|
|
180
366
|
```
|
|
181
367
|
|
|
182
368
|
## License
|
package/package.json
CHANGED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
const { Command } = require('commander');
|
|
2
|
+
const client = require('../lib/client');
|
|
3
|
+
const { output, error } = require('../lib/output');
|
|
4
|
+
const { withSpinner } = require('../utils/spinner');
|
|
5
|
+
const { formatRelativeTime } = require('../utils/format');
|
|
6
|
+
|
|
7
|
+
const activity = new Command('activity')
|
|
8
|
+
.description('Activity log commands');
|
|
9
|
+
|
|
10
|
+
activity
|
|
11
|
+
.command('board <boardId>')
|
|
12
|
+
.description('Show activity log for a board')
|
|
13
|
+
.option('--limit <n>', 'Number of activities', '20')
|
|
14
|
+
.option('-f, --format <format>', 'Output format')
|
|
15
|
+
.action(async (boardId, options) => {
|
|
16
|
+
try {
|
|
17
|
+
const data = await withSpinner('Fetching activities...', () =>
|
|
18
|
+
client.get(`/boards/${boardId}/activities`)
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const activities = data.activities || data.data || data;
|
|
22
|
+
const actArr = Array.isArray(activities) ? activities : [];
|
|
23
|
+
const limited = actArr.slice(0, parseInt(options.limit));
|
|
24
|
+
|
|
25
|
+
const rows = limited.map(a => ({
|
|
26
|
+
action: a.action || a.type || 'unknown',
|
|
27
|
+
user: a.user?.userName || a.user?.name || 'N/A',
|
|
28
|
+
target: a.target || a.card?.title || '',
|
|
29
|
+
time: formatRelativeTime(a.createdAt),
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
output(rows, {
|
|
33
|
+
headers: ['Action', 'User', 'Target', 'Time'],
|
|
34
|
+
format: options.format,
|
|
35
|
+
});
|
|
36
|
+
} catch (err) {
|
|
37
|
+
error(`Failed to fetch activities: ${err.message}`);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
activity
|
|
42
|
+
.command('card <cardId>')
|
|
43
|
+
.description('Show activity log for a card')
|
|
44
|
+
.option('--limit <n>', 'Number of activities', '20')
|
|
45
|
+
.option('-f, --format <format>', 'Output format')
|
|
46
|
+
.action(async (cardId, options) => {
|
|
47
|
+
try {
|
|
48
|
+
const data = await withSpinner('Fetching activities...', () =>
|
|
49
|
+
client.get(`/cards/${cardId}/activities`)
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const activities = data.activities || data.data || data;
|
|
53
|
+
const actArr = Array.isArray(activities) ? activities : [];
|
|
54
|
+
const limited = actArr.slice(0, parseInt(options.limit));
|
|
55
|
+
|
|
56
|
+
const rows = limited.map(a => ({
|
|
57
|
+
action: a.action || a.type || 'unknown',
|
|
58
|
+
user: a.user?.userName || a.user?.name || 'N/A',
|
|
59
|
+
detail: (a.detail || a.description || '').substring(0, 60),
|
|
60
|
+
time: formatRelativeTime(a.createdAt),
|
|
61
|
+
}));
|
|
62
|
+
|
|
63
|
+
output(rows, {
|
|
64
|
+
headers: ['Action', 'User', 'Detail', 'Time'],
|
|
65
|
+
format: options.format,
|
|
66
|
+
});
|
|
67
|
+
} catch (err) {
|
|
68
|
+
error(`Failed to fetch activities: ${err.message}`);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
activity
|
|
73
|
+
.command('me')
|
|
74
|
+
.description('Show your recent activity')
|
|
75
|
+
.option('--limit <n>', 'Number of activities', '20')
|
|
76
|
+
.option('-f, --format <format>', 'Output format')
|
|
77
|
+
.action(async (options) => {
|
|
78
|
+
try {
|
|
79
|
+
const data = await withSpinner('Fetching activities...', () =>
|
|
80
|
+
client.get('/users/me/activities')
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const activities = data.activities || data.data || data;
|
|
84
|
+
const actArr = Array.isArray(activities) ? activities : [];
|
|
85
|
+
const limited = actArr.slice(0, parseInt(options.limit));
|
|
86
|
+
|
|
87
|
+
const rows = limited.map(a => ({
|
|
88
|
+
action: a.action || a.type || 'unknown',
|
|
89
|
+
target: a.target || a.card?.title || a.board?.name || '',
|
|
90
|
+
time: formatRelativeTime(a.createdAt),
|
|
91
|
+
}));
|
|
92
|
+
|
|
93
|
+
output(rows, {
|
|
94
|
+
headers: ['Action', 'Target', 'Time'],
|
|
95
|
+
format: options.format,
|
|
96
|
+
});
|
|
97
|
+
} catch (err) {
|
|
98
|
+
error(`Failed to fetch activities: ${err.message}`);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
module.exports = activity;
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
const { Command } = require('commander');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const config = require('../lib/config');
|
|
4
|
+
const client = require('../lib/client');
|
|
5
|
+
const { output, success, error } = require('../lib/output');
|
|
6
|
+
const { withSpinner } = require('../utils/spinner');
|
|
7
|
+
const { formatDuration } = require('../utils/format');
|
|
8
|
+
|
|
9
|
+
const analytics = new Command('analytics')
|
|
10
|
+
.alias('an')
|
|
11
|
+
.description('Analytics and reporting commands');
|
|
12
|
+
|
|
13
|
+
analytics
|
|
14
|
+
.command('board <boardId>')
|
|
15
|
+
.description('Show board analytics overview')
|
|
16
|
+
.action(async (boardId) => {
|
|
17
|
+
try {
|
|
18
|
+
const data = await withSpinner('Fetching analytics...', () =>
|
|
19
|
+
client.get(`/analytics/board/${boardId}`)
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const a = data.analytics || data.data || data;
|
|
23
|
+
console.log();
|
|
24
|
+
console.log(chalk.bold(' Board Analytics'));
|
|
25
|
+
console.log(chalk.bold(' ─────────────────'));
|
|
26
|
+
console.log(chalk.bold(' Total Cards: '), a.totalCards ?? 0);
|
|
27
|
+
console.log(chalk.bold(' Completed: '), a.completedCards ?? 0);
|
|
28
|
+
console.log(chalk.bold(' In Progress: '), a.inProgressCards ?? 0);
|
|
29
|
+
console.log(chalk.bold(' Overdue: '), a.overdueCards ?? 0);
|
|
30
|
+
console.log(chalk.bold(' Members: '), a.totalMembers ?? 0);
|
|
31
|
+
console.log(chalk.bold(' Completion Rate: '), `${a.completionRate ?? 0}%`);
|
|
32
|
+
console.log();
|
|
33
|
+
} catch (err) {
|
|
34
|
+
error(`Failed to fetch analytics: ${err.message}`);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
analytics
|
|
39
|
+
.command('metrics <boardId>')
|
|
40
|
+
.description('Show board metrics')
|
|
41
|
+
.option('--range <range>', 'Time range (7d|30d|90d)', '30d')
|
|
42
|
+
.option('-f, --format <format>', 'Output format')
|
|
43
|
+
.action(async (boardId, options) => {
|
|
44
|
+
try {
|
|
45
|
+
const data = await withSpinner('Fetching metrics...', () =>
|
|
46
|
+
client.get(`/analytics/board/${boardId}/metrics?range=${options.range}`)
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const metrics = data.metrics || data.data || data;
|
|
50
|
+
if (Array.isArray(metrics)) {
|
|
51
|
+
const rows = metrics.map(m => ({
|
|
52
|
+
metric: m.name || m.metric || 'N/A',
|
|
53
|
+
value: String(m.value ?? 0),
|
|
54
|
+
change: m.change ? `${m.change > 0 ? '+' : ''}${m.change}%` : '-',
|
|
55
|
+
}));
|
|
56
|
+
output(rows, {
|
|
57
|
+
headers: ['Metric', 'Value', 'Change'],
|
|
58
|
+
format: options.format,
|
|
59
|
+
});
|
|
60
|
+
} else {
|
|
61
|
+
console.log();
|
|
62
|
+
for (const [key, val] of Object.entries(metrics)) {
|
|
63
|
+
console.log(chalk.bold(` ${key}: `), val);
|
|
64
|
+
}
|
|
65
|
+
console.log();
|
|
66
|
+
}
|
|
67
|
+
} catch (err) {
|
|
68
|
+
error(`Failed to fetch metrics: ${err.message}`);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
analytics
|
|
73
|
+
.command('time <boardId>')
|
|
74
|
+
.description('Show time analytics for a board')
|
|
75
|
+
.option('-f, --format <format>', 'Output format')
|
|
76
|
+
.action(async (boardId, options) => {
|
|
77
|
+
try {
|
|
78
|
+
const data = await withSpinner('Fetching time analytics...', () =>
|
|
79
|
+
client.get(`/analytics/board/${boardId}/time-analytics`)
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const time = data.analytics || data.data || data;
|
|
83
|
+
if (Array.isArray(time)) {
|
|
84
|
+
const rows = time.map(t => ({
|
|
85
|
+
list: t.list || t.name || 'N/A',
|
|
86
|
+
avgTime: t.avgTime ? formatDuration(t.avgTime) : '-',
|
|
87
|
+
totalCards: String(t.totalCards ?? 0),
|
|
88
|
+
}));
|
|
89
|
+
output(rows, {
|
|
90
|
+
headers: ['List', 'Avg Time', 'Cards'],
|
|
91
|
+
format: options.format,
|
|
92
|
+
});
|
|
93
|
+
} else {
|
|
94
|
+
console.log();
|
|
95
|
+
console.log(chalk.bold(' Avg Completion: '), time.avgCompletion ? formatDuration(time.avgCompletion) : 'N/A');
|
|
96
|
+
console.log(chalk.bold(' Total Tracked: '), time.totalTracked ? formatDuration(time.totalTracked) : '0s');
|
|
97
|
+
console.log();
|
|
98
|
+
}
|
|
99
|
+
} catch (err) {
|
|
100
|
+
error(`Failed to fetch time analytics: ${err.message}`);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
analytics
|
|
105
|
+
.command('timeline <boardId>')
|
|
106
|
+
.description('Show full timeline for a board')
|
|
107
|
+
.option('-f, --format <format>', 'Output format')
|
|
108
|
+
.action(async (boardId, options) => {
|
|
109
|
+
try {
|
|
110
|
+
const data = await withSpinner('Fetching timeline...', () =>
|
|
111
|
+
client.get(`/analytics/board/${boardId}/full-timeline`)
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const timeline = data.timeline || data.data || data;
|
|
115
|
+
const rows = (Array.isArray(timeline) ? timeline : []).map(t => ({
|
|
116
|
+
date: t.date || 'N/A',
|
|
117
|
+
created: String(t.created ?? 0),
|
|
118
|
+
completed: String(t.completed ?? 0),
|
|
119
|
+
active: String(t.active ?? 0),
|
|
120
|
+
}));
|
|
121
|
+
|
|
122
|
+
output(rows, {
|
|
123
|
+
headers: ['Date', 'Created', 'Completed', 'Active'],
|
|
124
|
+
format: options.format,
|
|
125
|
+
});
|
|
126
|
+
} catch (err) {
|
|
127
|
+
error(`Failed to fetch timeline: ${err.message}`);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
analytics
|
|
132
|
+
.command('productivity <boardId>')
|
|
133
|
+
.description('Show user productivity for a board')
|
|
134
|
+
.option('-f, --format <format>', 'Output format')
|
|
135
|
+
.action(async (boardId, options) => {
|
|
136
|
+
try {
|
|
137
|
+
const data = await withSpinner('Fetching productivity...', () =>
|
|
138
|
+
client.get(`/analytics/board/${boardId}/user-productivity`)
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const users = data.users || data.data || data;
|
|
142
|
+
const rows = (Array.isArray(users) ? users : []).map(u => ({
|
|
143
|
+
user: u.userName || u.name || 'N/A',
|
|
144
|
+
completed: String(u.completedCards ?? 0),
|
|
145
|
+
assigned: String(u.assignedCards ?? 0),
|
|
146
|
+
timeTracked: u.timeTracked ? formatDuration(u.timeTracked) : '-',
|
|
147
|
+
}));
|
|
148
|
+
|
|
149
|
+
output(rows, {
|
|
150
|
+
headers: ['User', 'Completed', 'Assigned', 'Time Tracked'],
|
|
151
|
+
format: options.format,
|
|
152
|
+
});
|
|
153
|
+
} catch (err) {
|
|
154
|
+
error(`Failed to fetch productivity: ${err.message}`);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
analytics
|
|
159
|
+
.command('workflow <boardId>')
|
|
160
|
+
.description('Show workflow analytics for a board')
|
|
161
|
+
.option('-f, --format <format>', 'Output format')
|
|
162
|
+
.action(async (boardId, options) => {
|
|
163
|
+
try {
|
|
164
|
+
const data = await withSpinner('Fetching workflow analytics...', () =>
|
|
165
|
+
client.get(`/analytics/board/${boardId}/workflow-analytics`)
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const workflow = data.workflow || data.data || data;
|
|
169
|
+
const rows = (Array.isArray(workflow) ? workflow : []).map(w => ({
|
|
170
|
+
list: w.list || w.name || 'N/A',
|
|
171
|
+
cards: String(w.cardCount ?? 0),
|
|
172
|
+
avgTime: w.avgTime ? formatDuration(w.avgTime) : '-',
|
|
173
|
+
throughput: String(w.throughput ?? 0),
|
|
174
|
+
}));
|
|
175
|
+
|
|
176
|
+
output(rows, {
|
|
177
|
+
headers: ['List', 'Cards', 'Avg Time', 'Throughput'],
|
|
178
|
+
format: options.format,
|
|
179
|
+
});
|
|
180
|
+
} catch (err) {
|
|
181
|
+
error(`Failed to fetch workflow analytics: ${err.message}`);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
analytics
|
|
186
|
+
.command('workspace')
|
|
187
|
+
.description('Show workspace analytics')
|
|
188
|
+
.action(async () => {
|
|
189
|
+
try {
|
|
190
|
+
const workspaceId = config.get('activeWorkspace');
|
|
191
|
+
if (!workspaceId) return error('No active workspace.');
|
|
192
|
+
|
|
193
|
+
const data = await withSpinner('Fetching workspace analytics...', () =>
|
|
194
|
+
client.get(`/analytics/workspace/${workspaceId}`)
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
const a = data.analytics || data.data || data;
|
|
198
|
+
console.log();
|
|
199
|
+
console.log(chalk.bold(' Workspace Analytics'));
|
|
200
|
+
console.log(chalk.bold(' ─────────────────────'));
|
|
201
|
+
console.log(chalk.bold(' Total Boards: '), a.totalBoards ?? 0);
|
|
202
|
+
console.log(chalk.bold(' Total Cards: '), a.totalCards ?? 0);
|
|
203
|
+
console.log(chalk.bold(' Active Members:'), a.activeMembers ?? 0);
|
|
204
|
+
console.log(chalk.bold(' Completion: '), `${a.completionRate ?? 0}%`);
|
|
205
|
+
console.log();
|
|
206
|
+
} catch (err) {
|
|
207
|
+
error(`Failed to fetch workspace analytics: ${err.message}`);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
analytics
|
|
212
|
+
.command('user')
|
|
213
|
+
.description('Show your personal analytics')
|
|
214
|
+
.action(async () => {
|
|
215
|
+
try {
|
|
216
|
+
const userId = config.get('userId');
|
|
217
|
+
const data = await withSpinner('Fetching user analytics...', () =>
|
|
218
|
+
client.get(`/analytics/user/${userId}`)
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
const a = data.analytics || data.data || data;
|
|
222
|
+
console.log();
|
|
223
|
+
console.log(chalk.bold(' Your Analytics'));
|
|
224
|
+
console.log(chalk.bold(' ──────────────'));
|
|
225
|
+
console.log(chalk.bold(' Cards Completed: '), a.completedCards ?? 0);
|
|
226
|
+
console.log(chalk.bold(' Cards Assigned: '), a.assignedCards ?? 0);
|
|
227
|
+
console.log(chalk.bold(' Time Tracked: '), a.timeTracked ? formatDuration(a.timeTracked) : '0s');
|
|
228
|
+
console.log(chalk.bold(' Active Boards: '), a.activeBoards ?? 0);
|
|
229
|
+
console.log();
|
|
230
|
+
} catch (err) {
|
|
231
|
+
error(`Failed to fetch user analytics: ${err.message}`);
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
analytics
|
|
236
|
+
.command('trends')
|
|
237
|
+
.description('Show analytics trends')
|
|
238
|
+
.option('-f, --format <format>', 'Output format')
|
|
239
|
+
.action(async (options) => {
|
|
240
|
+
try {
|
|
241
|
+
const data = await withSpinner('Fetching trends...', () =>
|
|
242
|
+
client.get('/analytics/trends')
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
const trends = data.trends || data.data || data;
|
|
246
|
+
const rows = (Array.isArray(trends) ? trends : []).map(t => ({
|
|
247
|
+
period: t.period || t.date || 'N/A',
|
|
248
|
+
cards: String(t.cardsCreated ?? 0),
|
|
249
|
+
completed: String(t.cardsCompleted ?? 0),
|
|
250
|
+
members: String(t.activeMembers ?? 0),
|
|
251
|
+
}));
|
|
252
|
+
|
|
253
|
+
output(rows, {
|
|
254
|
+
headers: ['Period', 'Created', 'Completed', 'Active Members'],
|
|
255
|
+
format: options.format,
|
|
256
|
+
});
|
|
257
|
+
} catch (err) {
|
|
258
|
+
error(`Failed to fetch trends: ${err.message}`);
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
analytics
|
|
263
|
+
.command('export <boardId>')
|
|
264
|
+
.description('Export board analytics')
|
|
265
|
+
.option('-o, --output <file>', 'Output file path', 'analytics-export.json')
|
|
266
|
+
.action(async (boardId, options) => {
|
|
267
|
+
try {
|
|
268
|
+
const data = await withSpinner('Exporting analytics...', () =>
|
|
269
|
+
client.get(`/analytics/export/${boardId}`)
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
const fs = require('fs');
|
|
273
|
+
const exportData = data.export || data.data || data;
|
|
274
|
+
fs.writeFileSync(options.output, JSON.stringify(exportData, null, 2));
|
|
275
|
+
success(`Analytics exported to ${chalk.bold(options.output)}`);
|
|
276
|
+
} catch (err) {
|
|
277
|
+
error(`Failed to export analytics: ${err.message}`);
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
module.exports = analytics;
|