@zoobbe/cli 1.0.0 → 1.1.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 +172 -0
- package/package.json +5 -4
- package/src/commands/ai.js +2 -1
- package/src/commands/board.js +17 -9
- package/src/commands/card.js +115 -41
- package/src/commands/page.js +2 -2
- package/src/commands/search.js +15 -12
- package/src/commands/status.js +3 -3
- package/src/commands/workspace.js +6 -2
package/README.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# @zoobbe/cli
|
|
2
|
+
|
|
3
|
+
Manage your [Zoobbe](https://zoobbe.com) boards, cards, and projects from the terminal.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @zoobbe/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Node.js 18 or later.
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Authenticate with your API key
|
|
17
|
+
zoobbe auth login --token zb_live_xxxxx
|
|
18
|
+
|
|
19
|
+
# Or point to a self-hosted instance
|
|
20
|
+
zoobbe config set apiUrl https://your-instance.com
|
|
21
|
+
zoobbe auth login --token zb_live_xxxxx
|
|
22
|
+
|
|
23
|
+
# Set your active workspace
|
|
24
|
+
zoobbe workspace list
|
|
25
|
+
zoobbe workspace switch <workspace-id>
|
|
26
|
+
|
|
27
|
+
# Start managing your work
|
|
28
|
+
zoobbe board list
|
|
29
|
+
zoobbe card list --board <board-id>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Authentication
|
|
33
|
+
|
|
34
|
+
Generate an API key from your workspace settings at **Settings > API Keys**, then:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
zoobbe auth login --token zb_live_your_api_key_here
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Check your login status:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
zoobbe auth whoami # Show current user
|
|
44
|
+
zoobbe auth status # Show auth details
|
|
45
|
+
zoobbe auth logout # Clear credentials
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Commands
|
|
49
|
+
|
|
50
|
+
### Workspaces
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
zoobbe workspace list # List your workspaces
|
|
54
|
+
zoobbe workspace switch <id> # Set active workspace
|
|
55
|
+
zoobbe workspace info [id] # Show workspace details
|
|
56
|
+
zoobbe workspace members # List workspace members
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Boards
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
zoobbe board list # List boards in active workspace
|
|
63
|
+
zoobbe board list --all # Include archived boards
|
|
64
|
+
zoobbe board create <name> # Create a new board
|
|
65
|
+
zoobbe board create <name> -v Public # Create with visibility
|
|
66
|
+
zoobbe board info <id> # Show board details
|
|
67
|
+
zoobbe board open <id> # Open board in browser
|
|
68
|
+
zoobbe board archive <id> # Archive a board
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Cards
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
zoobbe card list --board <id> # List cards on a board
|
|
75
|
+
zoobbe card create <title> -b <board-id> # Create card in first list
|
|
76
|
+
zoobbe card create <title> -b <id> -l <list-id> # Create card in specific list
|
|
77
|
+
zoobbe card create <title> -b <id> --due 2026-04-01 --priority high
|
|
78
|
+
zoobbe card info <card-id> # Show card details
|
|
79
|
+
zoobbe card move <card-id> -l <list-name> # Move card to another list
|
|
80
|
+
zoobbe card assign <card-id> -u <user-id> # Assign a user
|
|
81
|
+
zoobbe card comment <card-id> "your message" # Add a comment
|
|
82
|
+
zoobbe card done <card-id> # Mark card as complete
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Pages
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
zoobbe page list # List pages
|
|
89
|
+
zoobbe page create <title> # Create a new page
|
|
90
|
+
zoobbe page info <page-id> # Show page details
|
|
91
|
+
zoobbe page open <page-id> # Open page in browser
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Search
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
zoobbe search "query" # Search across boards, cards, and pages
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Status
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
zoobbe status # Show your cards across all boards
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### AI (Experimental)
|
|
107
|
+
|
|
108
|
+
Requires an AI provider key configured on the backend.
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
zoobbe ask "what cards are due this week?"
|
|
112
|
+
zoobbe ask "move all overdue cards to Review"
|
|
113
|
+
zoobbe ask "create a sprint board with 3 lists" --provider openai
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Configuration
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
zoobbe config set apiUrl https://api.zoobbe.com # Set API URL
|
|
120
|
+
zoobbe config set format json # Default output format
|
|
121
|
+
zoobbe config get apiUrl # Get a config value
|
|
122
|
+
zoobbe config list # Show all config
|
|
123
|
+
zoobbe config path # Show config file location
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Output Formats
|
|
127
|
+
|
|
128
|
+
Use the `--format` flag or set a default:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
zoobbe board list --format json # JSON output
|
|
132
|
+
zoobbe board list --format plain # Plain text
|
|
133
|
+
zoobbe board list --format table # Table (default)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Global Options
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
-f, --format <format> Output format: table | json | plain
|
|
140
|
+
--workspace <id> Override active workspace for this command
|
|
141
|
+
-V, --version Show version
|
|
142
|
+
-h, --help Show help
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Aliases
|
|
146
|
+
|
|
147
|
+
Most commands have short aliases:
|
|
148
|
+
|
|
149
|
+
| Command | Alias |
|
|
150
|
+
|-------------|-------|
|
|
151
|
+
| `board` | `b` |
|
|
152
|
+
| `card` | `c` |
|
|
153
|
+
| `page` | `p` |
|
|
154
|
+
| `list` | `ls` |
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
zoobbe b ls # Same as: zoobbe board list
|
|
158
|
+
zoobbe c ls -b <id> # Same as: zoobbe card list --board <id>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Self-Hosted
|
|
162
|
+
|
|
163
|
+
For self-hosted Zoobbe instances, set your API URL before logging in:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
zoobbe config set apiUrl https://your-instance.com
|
|
167
|
+
zoobbe auth login --token zb_live_xxxxx
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zoobbe/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Zoobbe CLI - Manage boards, cards, and projects from the terminal",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"zoobbe": "
|
|
7
|
+
"zoobbe": "bin/zoobbe"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node bin/zoobbe",
|
|
@@ -29,14 +29,15 @@
|
|
|
29
29
|
"homepage": "https://zoobbe.com/cli",
|
|
30
30
|
"repository": {
|
|
31
31
|
"type": "git",
|
|
32
|
-
"url": "https://github.com/zoobbe/zoobbe-cli"
|
|
32
|
+
"url": "git+https://github.com/zoobbe/zoobbe-cli.git"
|
|
33
33
|
},
|
|
34
34
|
"bugs": {
|
|
35
35
|
"url": "https://github.com/zoobbe/zoobbe-cli/issues"
|
|
36
36
|
},
|
|
37
37
|
"files": [
|
|
38
38
|
"bin/",
|
|
39
|
-
"src/"
|
|
39
|
+
"src/",
|
|
40
|
+
"README.md"
|
|
40
41
|
],
|
|
41
42
|
"dependencies": {
|
|
42
43
|
"chalk": "^4.1.2",
|
package/src/commands/ai.js
CHANGED
|
@@ -73,7 +73,8 @@ const ai = new Command('ask')
|
|
|
73
73
|
console.log(JSON.stringify(result, null, 2));
|
|
74
74
|
}
|
|
75
75
|
} catch (err) {
|
|
76
|
-
|
|
76
|
+
const detail = err.data?.message || err.message;
|
|
77
|
+
error(`AI command failed: ${detail}`);
|
|
77
78
|
}
|
|
78
79
|
});
|
|
79
80
|
|
package/src/commands/board.js
CHANGED
|
@@ -60,16 +60,24 @@ board
|
|
|
60
60
|
return error('No active workspace. Run "zoobbe workspace switch <name>".');
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
// Resolve workspace ObjectId from shortId
|
|
64
|
+
const wsData = await client.get('/workspaces/me');
|
|
65
|
+
const workspaces = wsData.data || wsData;
|
|
66
|
+
const ws = workspaces.find(w => w.shortId === workspaceId);
|
|
67
|
+
if (!ws) {
|
|
68
|
+
return error('Active workspace not found.');
|
|
69
|
+
}
|
|
70
|
+
|
|
63
71
|
const data = await withSpinner('Creating board...', () =>
|
|
64
72
|
client.post('/boards', {
|
|
65
|
-
name,
|
|
66
|
-
workspaceId,
|
|
73
|
+
title: name,
|
|
74
|
+
workspaceId: ws._id,
|
|
67
75
|
visibility: options.visibility,
|
|
68
76
|
})
|
|
69
77
|
);
|
|
70
78
|
|
|
71
|
-
const b = data.data || data;
|
|
72
|
-
success(`Created board: ${chalk.bold(b.name || name)} (${b.shortId})`);
|
|
79
|
+
const b = data.board || data.data || data;
|
|
80
|
+
success(`Created board: ${chalk.bold(b.title || b.name || name)} (${b.shortId})`);
|
|
73
81
|
} catch (err) {
|
|
74
82
|
error(`Failed to create board: ${err.message}`);
|
|
75
83
|
}
|
|
@@ -91,7 +99,7 @@ board
|
|
|
91
99
|
const boards = data.data || data;
|
|
92
100
|
const match = boards.find(b =>
|
|
93
101
|
b.shortId === nameOrId ||
|
|
94
|
-
(b.name || '').toLowerCase() === nameOrId.toLowerCase()
|
|
102
|
+
(b.title || b.name || '').toLowerCase() === nameOrId.toLowerCase()
|
|
95
103
|
);
|
|
96
104
|
|
|
97
105
|
const boardId = match ? match.shortId : nameOrId;
|
|
@@ -113,7 +121,7 @@ board
|
|
|
113
121
|
const boards = data.data || data;
|
|
114
122
|
const match = boards.find(b =>
|
|
115
123
|
b.shortId === nameOrId ||
|
|
116
|
-
(b.name || '').toLowerCase() === nameOrId.toLowerCase()
|
|
124
|
+
(b.title || b.name || '').toLowerCase() === nameOrId.toLowerCase()
|
|
117
125
|
);
|
|
118
126
|
|
|
119
127
|
if (!match) {
|
|
@@ -124,7 +132,7 @@ board
|
|
|
124
132
|
client.post(`/boards/archive/${match.shortId}`)
|
|
125
133
|
);
|
|
126
134
|
|
|
127
|
-
success(`Archived board: ${chalk.bold(match.name)}`);
|
|
135
|
+
success(`Archived board: ${chalk.bold(match.title || match.name)}`);
|
|
128
136
|
} catch (err) {
|
|
129
137
|
error(`Failed to archive board: ${err.message}`);
|
|
130
138
|
}
|
|
@@ -139,9 +147,9 @@ board
|
|
|
139
147
|
client.get(`/boards/${nameOrId}`)
|
|
140
148
|
);
|
|
141
149
|
|
|
142
|
-
const b = data.data || data;
|
|
150
|
+
const b = data.board || data.data || data;
|
|
143
151
|
console.log();
|
|
144
|
-
console.log(chalk.bold(' Name: '), b.name);
|
|
152
|
+
console.log(chalk.bold(' Name: '), b.title || b.name);
|
|
145
153
|
console.log(chalk.bold(' ID: '), b.shortId);
|
|
146
154
|
console.log(chalk.bold(' Visibility: '), b.visibility || 'Private');
|
|
147
155
|
console.log(chalk.bold(' Members: '), b.members?.length || 0);
|
package/src/commands/card.js
CHANGED
|
@@ -20,30 +20,43 @@ card
|
|
|
20
20
|
.option('-f, --format <format>', 'Output format')
|
|
21
21
|
.action(async (options) => {
|
|
22
22
|
try {
|
|
23
|
-
let
|
|
24
|
-
|
|
25
|
-
if (options.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
23
|
+
let cards = [];
|
|
24
|
+
|
|
25
|
+
if (options.board) {
|
|
26
|
+
// Get lists with populated cards for this board
|
|
27
|
+
const listsData = await withSpinner('Fetching cards...', () =>
|
|
28
|
+
client.get(`/boards/${options.board}/lists`)
|
|
29
|
+
);
|
|
30
|
+
const lists = listsData.lists || listsData.data || listsData;
|
|
31
|
+
const listArr = Array.isArray(lists) ? lists : Object.values(lists);
|
|
32
|
+
|
|
33
|
+
// Collect card shortIds per list
|
|
34
|
+
const cardRefs = [];
|
|
35
|
+
for (const list of listArr) {
|
|
36
|
+
if (list.cards && Array.isArray(list.cards)) {
|
|
37
|
+
for (const c of list.cards) {
|
|
38
|
+
cardRefs.push({ id: c.shortId || c._id, listName: list.title || '' });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Fetch full card details in parallel
|
|
44
|
+
const results = await Promise.allSettled(
|
|
45
|
+
cardRefs.map(ref => client.get(`/cards/${ref.id}`).then(d => ({ ...d, _listName: ref.listName })))
|
|
46
|
+
);
|
|
47
|
+
for (const r of results) {
|
|
48
|
+
if (r.status === 'fulfilled') cards.push(r.value);
|
|
49
|
+
}
|
|
32
50
|
} else {
|
|
33
51
|
// Get cards for current user
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
52
|
+
const userId = config.get('userId');
|
|
53
|
+
const data = await withSpinner('Fetching cards...', () =>
|
|
54
|
+
client.get(`/${userId}/cards`)
|
|
55
|
+
);
|
|
56
|
+
cards = data.data || data;
|
|
57
|
+
if (!Array.isArray(cards)) cards = [];
|
|
39
58
|
}
|
|
40
59
|
|
|
41
|
-
const data = await withSpinner('Fetching cards...', () =>
|
|
42
|
-
client.get(path)
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
let cards = data.data || data;
|
|
46
|
-
|
|
47
60
|
// Apply due date filter
|
|
48
61
|
if (options.due && Array.isArray(cards)) {
|
|
49
62
|
const now = new Date();
|
|
@@ -62,9 +75,9 @@ card
|
|
|
62
75
|
const rows = (Array.isArray(cards) ? cards : []).map(c => ({
|
|
63
76
|
title: (c.title || c.name || '').substring(0, 50),
|
|
64
77
|
id: c.shortId || c._id,
|
|
65
|
-
list: c.
|
|
66
|
-
due: c.dueDate ? new Date(c.dueDate).toLocaleDateString() : '-',
|
|
67
|
-
members: String(c.members?.length || 0),
|
|
78
|
+
list: c._listName || c.actionListTitle || c.actionList?.name || '',
|
|
79
|
+
due: c.dueDate?.date ? new Date(c.dueDate.date).toLocaleDateString() : (typeof c.dueDate === 'string' && c.dueDate ? new Date(c.dueDate).toLocaleDateString() : '-'),
|
|
80
|
+
members: String(c.members?.length || c.users?.length || 0),
|
|
68
81
|
priority: c.priority || '-',
|
|
69
82
|
}));
|
|
70
83
|
|
|
@@ -96,29 +109,41 @@ card
|
|
|
96
109
|
// If no list specified, get the first list from the board
|
|
97
110
|
if (!listId) {
|
|
98
111
|
const listsData = await client.get(`/boards/${options.board}/lists`);
|
|
99
|
-
const lists = listsData.data || listsData;
|
|
100
|
-
|
|
112
|
+
const lists = listsData.lists || listsData.data || listsData;
|
|
113
|
+
const listArr = Array.isArray(lists) ? lists : Object.values(lists);
|
|
114
|
+
if (listArr.length === 0) {
|
|
101
115
|
return error('Board has no lists. Create one first.');
|
|
102
116
|
}
|
|
103
|
-
listId =
|
|
117
|
+
listId = listArr[0]._id;
|
|
104
118
|
}
|
|
105
119
|
|
|
106
120
|
const body = {
|
|
107
121
|
title,
|
|
108
|
-
boardId: options.board,
|
|
109
122
|
actionListId: listId,
|
|
110
123
|
};
|
|
111
124
|
|
|
112
125
|
if (options.description) body.description = options.description;
|
|
113
|
-
if (options.due) body.dueDate = options.due;
|
|
114
|
-
if (options.priority) body.priority = options.priority;
|
|
115
126
|
|
|
116
127
|
const data = await withSpinner('Creating card...', () =>
|
|
117
128
|
client.post('/cards', body)
|
|
118
129
|
);
|
|
119
130
|
|
|
120
|
-
const c = data.data || data;
|
|
131
|
+
const c = data.card || data.data || data;
|
|
121
132
|
success(`Created card: ${chalk.bold(title)} (${c.shortId || c._id})`);
|
|
133
|
+
|
|
134
|
+
// Set due date if provided
|
|
135
|
+
if (options.due && (c.shortId || c._id)) {
|
|
136
|
+
await client.post(`/cards/${c.shortId || c._id}/duedate`, {
|
|
137
|
+
dueDate: options.due,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Set priority if provided
|
|
142
|
+
if (options.priority && (c.shortId || c._id)) {
|
|
143
|
+
await client.post(`/cards/${c.shortId || c._id}/priority`, {
|
|
144
|
+
priority: options.priority,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
122
147
|
} catch (err) {
|
|
123
148
|
error(`Failed to create card: ${err.message}`);
|
|
124
149
|
}
|
|
@@ -127,20 +152,62 @@ card
|
|
|
127
152
|
card
|
|
128
153
|
.command('move <cardId>')
|
|
129
154
|
.description('Move a card to a different list')
|
|
130
|
-
.option('-l, --list <
|
|
155
|
+
.option('-l, --list <listName>', 'Target list name or ID (required)')
|
|
156
|
+
.option('-b, --board <boardId>', 'Board ID (auto-detected from card if not specified)')
|
|
131
157
|
.action(async (cardId, options) => {
|
|
132
158
|
try {
|
|
133
159
|
if (!options.list) {
|
|
134
|
-
return error('Target list
|
|
160
|
+
return error('Target list is required. Use -l <listName>.');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Fetch card to get board and current list info
|
|
164
|
+
const cardData = await withSpinner('Fetching card...', () =>
|
|
165
|
+
client.get(`/cards/${cardId}`)
|
|
166
|
+
);
|
|
167
|
+
const c = cardData.card || cardData.data || cardData;
|
|
168
|
+
const boardId = options.board || c.boardShortId || c.board;
|
|
169
|
+
|
|
170
|
+
if (!boardId) {
|
|
171
|
+
return error('Could not determine board. Use -b <boardId>.');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Get all lists for the board
|
|
175
|
+
const listsData = await client.get(`/boards/${boardId}/lists`);
|
|
176
|
+
const lists = listsData.lists || listsData.data || listsData;
|
|
177
|
+
const listArr = Array.isArray(lists) ? lists : Object.values(lists);
|
|
178
|
+
|
|
179
|
+
// Find source list (the list the card is currently in)
|
|
180
|
+
const sourceActionListId = c.actionList?._id || c.actionListId;
|
|
181
|
+
const sourceList = listArr.find(l =>
|
|
182
|
+
l._id === sourceActionListId || l._id?.toString() === sourceActionListId?.toString()
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
// Find target list by name or ID
|
|
186
|
+
const targetList = listArr.find(l =>
|
|
187
|
+
l._id === options.list ||
|
|
188
|
+
l._id?.toString() === options.list ||
|
|
189
|
+
(l.title || '').toLowerCase() === options.list.toLowerCase()
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
if (!targetList) {
|
|
193
|
+
const available = listArr.map(l => ` - ${l.title} (${l._id})`).join('\n');
|
|
194
|
+
return error(`List "${options.list}" not found. Available lists:\n${available}`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (!sourceList) {
|
|
198
|
+
return error('Could not determine the card\'s current list.');
|
|
135
199
|
}
|
|
136
200
|
|
|
137
201
|
await withSpinner('Moving card...', () =>
|
|
138
|
-
client.
|
|
139
|
-
|
|
202
|
+
client.put(`/boards/${boardId}/cards/order`, {
|
|
203
|
+
sourceListId: sourceList._id,
|
|
204
|
+
cardId: c._id,
|
|
205
|
+
targetListId: targetList._id,
|
|
206
|
+
targetPosition: 0,
|
|
140
207
|
})
|
|
141
208
|
);
|
|
142
209
|
|
|
143
|
-
success(`Moved card ${cardId} to
|
|
210
|
+
success(`Moved card ${cardId} to ${chalk.bold(targetList.title)}`);
|
|
144
211
|
} catch (err) {
|
|
145
212
|
error(`Failed to move card: ${err.message}`);
|
|
146
213
|
}
|
|
@@ -173,8 +240,13 @@ card
|
|
|
173
240
|
.description('Add a comment to a card')
|
|
174
241
|
.action(async (cardId, message) => {
|
|
175
242
|
try {
|
|
243
|
+
const userId = config.get('userId');
|
|
176
244
|
await withSpinner('Adding comment...', () =>
|
|
177
|
-
client.post(`/cards/${cardId}/comments`, {
|
|
245
|
+
client.post(`/cards/${cardId}/comments`, {
|
|
246
|
+
comment: message,
|
|
247
|
+
member: userId,
|
|
248
|
+
mentionedIds: [],
|
|
249
|
+
})
|
|
178
250
|
);
|
|
179
251
|
|
|
180
252
|
success(`Comment added to card ${cardId}`);
|
|
@@ -207,15 +279,17 @@ card
|
|
|
207
279
|
client.get(`/cards/${cardId}`)
|
|
208
280
|
);
|
|
209
281
|
|
|
210
|
-
const c = data.data || data;
|
|
282
|
+
const c = data.card || data.data || data;
|
|
283
|
+
const dueStr = c.dueDate?.date ? new Date(c.dueDate.date).toLocaleDateString() : 'None';
|
|
211
284
|
console.log();
|
|
212
285
|
console.log(chalk.bold(' Title: '), c.title || c.name);
|
|
213
286
|
console.log(chalk.bold(' ID: '), c.shortId || c._id);
|
|
214
|
-
console.log(chalk.bold('
|
|
215
|
-
console.log(chalk.bold('
|
|
216
|
-
console.log(chalk.bold('
|
|
287
|
+
console.log(chalk.bold(' Board: '), c.boardTitle || c.board || 'N/A');
|
|
288
|
+
console.log(chalk.bold(' List: '), c.actionListTitle || c.actionList?.name || 'N/A');
|
|
289
|
+
console.log(chalk.bold(' Members: '), c.members?.length || c.users?.length || 0);
|
|
290
|
+
console.log(chalk.bold(' Due Date: '), dueStr);
|
|
217
291
|
console.log(chalk.bold(' Priority: '), c.priority || 'None');
|
|
218
|
-
console.log(chalk.bold(' Complete: '), c.
|
|
292
|
+
console.log(chalk.bold(' Complete: '), c.completed ? 'Yes' : 'No');
|
|
219
293
|
if (c.description) {
|
|
220
294
|
console.log(chalk.bold(' Description:'), c.description.substring(0, 100));
|
|
221
295
|
}
|
package/src/commands/page.js
CHANGED
|
@@ -54,7 +54,7 @@ page
|
|
|
54
54
|
client.post('/pages', { title, workspaceId })
|
|
55
55
|
);
|
|
56
56
|
|
|
57
|
-
const p = data.data || data;
|
|
57
|
+
const p = data.page || data.data || data;
|
|
58
58
|
success(`Created page: ${chalk.bold(title)} (${p.shortId || p._id})`);
|
|
59
59
|
} catch (err) {
|
|
60
60
|
error(`Failed to create page: ${err.message}`);
|
|
@@ -85,7 +85,7 @@ page
|
|
|
85
85
|
client.get(`/pages/${pageId}`)
|
|
86
86
|
);
|
|
87
87
|
|
|
88
|
-
const p = data.data || data;
|
|
88
|
+
const p = data.page || data.data || data;
|
|
89
89
|
console.log();
|
|
90
90
|
console.log(chalk.bold(' Title: '), p.title || 'Untitled');
|
|
91
91
|
console.log(chalk.bold(' ID: '), p.shortId || p._id);
|
package/src/commands/search.js
CHANGED
|
@@ -15,25 +15,28 @@ const search = new Command('search')
|
|
|
15
15
|
const workspaceId = config.get('activeWorkspace');
|
|
16
16
|
|
|
17
17
|
const data = await withSpinner(`Searching for "${query}"...`, () =>
|
|
18
|
-
client.post(
|
|
19
|
-
query,
|
|
20
|
-
workspaceId,
|
|
21
|
-
})
|
|
18
|
+
client.post(`/global/search?query=${encodeURIComponent(query)}`)
|
|
22
19
|
);
|
|
23
20
|
|
|
24
|
-
const results = data.data || data;
|
|
21
|
+
const results = data.data || data.results || data;
|
|
25
22
|
|
|
26
|
-
|
|
23
|
+
// Flatten different result types into a single list
|
|
24
|
+
const items = [];
|
|
25
|
+
if (results.boards) results.boards.forEach(b => items.push({ type: 'Board', title: b.title || b.name, id: b.shortId || b._id }));
|
|
26
|
+
if (results.cards) results.cards.forEach(c => items.push({ type: 'Card', title: c.title || c.name, id: c.shortId || c._id, context: c.boardTitle || '' }));
|
|
27
|
+
if (results.pages) results.pages.forEach(p => items.push({ type: 'Page', title: p.title || p.name, id: p.shortId || p._id }));
|
|
28
|
+
if (Array.isArray(results)) results.forEach(r => items.push({ type: r.type || 'item', title: r.title || r.name || '', id: r.shortId || r._id || '' }));
|
|
29
|
+
|
|
30
|
+
if (items.length === 0) {
|
|
27
31
|
console.log(chalk.yellow(' No results found.'));
|
|
28
32
|
return;
|
|
29
33
|
}
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
context: r.boardName || r.workspaceName || '',
|
|
35
|
+
const rows = items.map(r => ({
|
|
36
|
+
type: r.type,
|
|
37
|
+
title: (r.title || '').substring(0, 50),
|
|
38
|
+
id: r.id || '',
|
|
39
|
+
context: r.context || '',
|
|
37
40
|
}));
|
|
38
41
|
|
|
39
42
|
output(rows, {
|
package/src/commands/status.js
CHANGED
|
@@ -12,13 +12,13 @@ const status = new Command('status')
|
|
|
12
12
|
.option('--overdue', 'Only show overdue cards')
|
|
13
13
|
.action(async (options) => {
|
|
14
14
|
try {
|
|
15
|
-
const
|
|
16
|
-
if (!
|
|
15
|
+
const userId = config.get('userId');
|
|
16
|
+
if (!userId) {
|
|
17
17
|
return error('Not logged in. Run "zoobbe auth login" first.');
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
const data = await withSpinner('Fetching your cards...', () =>
|
|
21
|
-
client.get(`/${
|
|
21
|
+
client.get(`/${userId}/cards`)
|
|
22
22
|
);
|
|
23
23
|
|
|
24
24
|
let cards = data.data || data;
|
|
@@ -129,10 +129,14 @@ workspace
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
const data = await withSpinner('Fetching workspace info...', () =>
|
|
132
|
-
client.get(
|
|
132
|
+
client.get('/workspaces/me')
|
|
133
133
|
);
|
|
134
134
|
|
|
135
|
-
const
|
|
135
|
+
const workspaces = data.data || data;
|
|
136
|
+
const ws = workspaces.find(w => w.shortId === workspaceId);
|
|
137
|
+
if (!ws) {
|
|
138
|
+
return error('Workspace not found.');
|
|
139
|
+
}
|
|
136
140
|
console.log();
|
|
137
141
|
console.log(chalk.bold(' Name: '), ws.name);
|
|
138
142
|
console.log(chalk.bold(' ID: '), ws.shortId);
|