@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 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 <id> # Set active workspace
76
- zoobbe workspace info [id] # Show workspace details
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 # Create with visibility
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 <board-id> # Create card in first list
97
- zoobbe card create <title> -b <id> -l <list-id> # Create card in specific list
98
- zoobbe card create <title> -b <id> --due 2026-04-01 --priority high
99
- zoobbe card info <card-id> # Show card details
100
- zoobbe card move <card-id> -l <list-name> # Move card to another list
101
- zoobbe card assign <card-id> -u <user-id> # Assign a user
102
- zoobbe card comment <card-id> "your message" # Add a comment
103
- zoobbe card done <card-id> # Mark card as complete
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 <page-id> # Show page details
112
- zoobbe page open <page-id> # Open page in browser
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 | Alias |
171
- |-------------|-------|
172
- | `board` | `b` |
173
- | `card` | `c` |
174
- | `page` | `p` |
175
- | `list` | `ls` |
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zoobbe/cli",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Zoobbe CLI - Manage boards, cards, and projects from the terminal",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -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;