@zoobbe/cli 1.2.0 → 1.2.2
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 +243 -19
- package/bin/zoobbe-mcp +5 -0
- package/package.json +6 -3
- 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/mcp-server.js +797 -0
- package/src/utils/format.js +39 -0
- package/src/utils/prompts.js +40 -0
- package/src/utils/resolve.js +67 -0
|
@@ -149,4 +149,178 @@ workspace
|
|
|
149
149
|
}
|
|
150
150
|
});
|
|
151
151
|
|
|
152
|
+
// ── Extended commands ──
|
|
153
|
+
|
|
154
|
+
const { confirmAction } = require('../utils/prompts');
|
|
155
|
+
const { resolveWorkspaceObjectId } = require('../utils/resolve');
|
|
156
|
+
|
|
157
|
+
workspace
|
|
158
|
+
.command('update')
|
|
159
|
+
.description('Update active workspace name')
|
|
160
|
+
.option('--name <name>', 'New workspace name (required)')
|
|
161
|
+
.action(async (options) => {
|
|
162
|
+
try {
|
|
163
|
+
if (!options.name) return error('Workspace name is required. Use --name <name>.');
|
|
164
|
+
|
|
165
|
+
const workspaceId = config.get('activeWorkspace');
|
|
166
|
+
if (!workspaceId) return error('No active workspace.');
|
|
167
|
+
|
|
168
|
+
const wsId = await resolveWorkspaceObjectId(workspaceId);
|
|
169
|
+
await withSpinner('Updating workspace...', () =>
|
|
170
|
+
client.put(`/workspaces/${wsId}`, { name: options.name })
|
|
171
|
+
);
|
|
172
|
+
config.set('activeWorkspaceName', options.name);
|
|
173
|
+
success(`Workspace renamed to: ${chalk.bold(options.name)}`);
|
|
174
|
+
} catch (err) {
|
|
175
|
+
error(`Failed to update workspace: ${err.message}`);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
workspace
|
|
180
|
+
.command('delete')
|
|
181
|
+
.description('Delete the active workspace')
|
|
182
|
+
.option('--force', 'Skip confirmation')
|
|
183
|
+
.action(async (options) => {
|
|
184
|
+
try {
|
|
185
|
+
const workspaceId = config.get('activeWorkspace');
|
|
186
|
+
const workspaceName = config.get('activeWorkspaceName');
|
|
187
|
+
if (!workspaceId) return error('No active workspace.');
|
|
188
|
+
|
|
189
|
+
if (!options.force) {
|
|
190
|
+
const confirmed = await confirmAction(`Permanently delete workspace "${workspaceName}" and all its data?`);
|
|
191
|
+
if (!confirmed) return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const wsId = await resolveWorkspaceObjectId(workspaceId);
|
|
195
|
+
await withSpinner('Deleting workspace...', () =>
|
|
196
|
+
client.post(`/workspaces/${wsId}`)
|
|
197
|
+
);
|
|
198
|
+
config.set('activeWorkspace', '');
|
|
199
|
+
config.set('activeWorkspaceName', '');
|
|
200
|
+
success(`Deleted workspace: ${chalk.bold(workspaceName)}`);
|
|
201
|
+
} catch (err) {
|
|
202
|
+
error(`Failed to delete workspace: ${err.message}`);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
workspace
|
|
207
|
+
.command('invite')
|
|
208
|
+
.description('Invite a user to the active workspace by email')
|
|
209
|
+
.option('-e, --email <email>', 'Email address (required)')
|
|
210
|
+
.option('--role <role>', 'Role (admin|member)', 'member')
|
|
211
|
+
.action(async (options) => {
|
|
212
|
+
try {
|
|
213
|
+
if (!options.email) return error('Email is required. Use -e <email>.');
|
|
214
|
+
|
|
215
|
+
const workspaceId = config.get('activeWorkspace');
|
|
216
|
+
if (!workspaceId) return error('No active workspace.');
|
|
217
|
+
|
|
218
|
+
const wsId = await resolveWorkspaceObjectId(workspaceId);
|
|
219
|
+
await withSpinner('Sending invitation...', () =>
|
|
220
|
+
client.post(`/workspace/${wsId}/invite`, {
|
|
221
|
+
email: options.email,
|
|
222
|
+
role: options.role,
|
|
223
|
+
})
|
|
224
|
+
);
|
|
225
|
+
success(`Invitation sent to ${chalk.bold(options.email)}`);
|
|
226
|
+
} catch (err) {
|
|
227
|
+
error(`Failed to invite: ${err.message}`);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
workspace
|
|
232
|
+
.command('join-link')
|
|
233
|
+
.description('Generate or delete workspace join link')
|
|
234
|
+
.option('--delete', 'Delete the existing join link')
|
|
235
|
+
.action(async (options) => {
|
|
236
|
+
try {
|
|
237
|
+
const workspaceId = config.get('activeWorkspace');
|
|
238
|
+
if (!workspaceId) return error('No active workspace.');
|
|
239
|
+
|
|
240
|
+
const wsId = await resolveWorkspaceObjectId(workspaceId);
|
|
241
|
+
|
|
242
|
+
if (options.delete) {
|
|
243
|
+
await withSpinner('Deleting join link...', () =>
|
|
244
|
+
client.delete(`/workspace/${wsId}/delete-join-link`)
|
|
245
|
+
);
|
|
246
|
+
success('Join link deleted');
|
|
247
|
+
} else {
|
|
248
|
+
const data = await withSpinner('Generating join link...', () =>
|
|
249
|
+
client.post(`/workspace/${wsId}/generate-join-link`)
|
|
250
|
+
);
|
|
251
|
+
const link = data.joinLink || data.link || data.data;
|
|
252
|
+
success(`Join link: ${chalk.bold(link)}`);
|
|
253
|
+
}
|
|
254
|
+
} catch (err) {
|
|
255
|
+
error(`Failed to manage join link: ${err.message}`);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
workspace
|
|
260
|
+
.command('add-member')
|
|
261
|
+
.description('Add a member to the active workspace')
|
|
262
|
+
.option('-u, --user <userId>', 'User ID (required)')
|
|
263
|
+
.action(async (options) => {
|
|
264
|
+
try {
|
|
265
|
+
if (!options.user) return error('User ID is required. Use -u <userId>.');
|
|
266
|
+
|
|
267
|
+
const workspaceId = config.get('activeWorkspace');
|
|
268
|
+
if (!workspaceId) return error('No active workspace.');
|
|
269
|
+
|
|
270
|
+
const wsId = await resolveWorkspaceObjectId(workspaceId);
|
|
271
|
+
await withSpinner('Adding member...', () =>
|
|
272
|
+
client.post(`/workspace/${wsId}/member`, { userId: options.user })
|
|
273
|
+
);
|
|
274
|
+
success('Member added to workspace');
|
|
275
|
+
} catch (err) {
|
|
276
|
+
error(`Failed to add member: ${err.message}`);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
workspace
|
|
281
|
+
.command('remove-member')
|
|
282
|
+
.description('Remove a member from the active workspace')
|
|
283
|
+
.option('-u, --user <userId>', 'User ID (required)')
|
|
284
|
+
.action(async (options) => {
|
|
285
|
+
try {
|
|
286
|
+
if (!options.user) return error('User ID is required. Use -u <userId>.');
|
|
287
|
+
|
|
288
|
+
const workspaceId = config.get('activeWorkspace');
|
|
289
|
+
if (!workspaceId) return error('No active workspace.');
|
|
290
|
+
|
|
291
|
+
const wsId = await resolveWorkspaceObjectId(workspaceId);
|
|
292
|
+
await withSpinner('Removing member...', () =>
|
|
293
|
+
client.delete(`/workspace/${wsId}/member`, { userId: options.user })
|
|
294
|
+
);
|
|
295
|
+
success('Member removed from workspace');
|
|
296
|
+
} catch (err) {
|
|
297
|
+
error(`Failed to remove member: ${err.message}`);
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
workspace
|
|
302
|
+
.command('member-role')
|
|
303
|
+
.description('Change a member\'s role in the active workspace')
|
|
304
|
+
.option('-u, --user <userId>', 'User ID (required)')
|
|
305
|
+
.option('--role <role>', 'New role (admin|member)', 'member')
|
|
306
|
+
.action(async (options) => {
|
|
307
|
+
try {
|
|
308
|
+
if (!options.user) return error('User ID is required. Use -u <userId>.');
|
|
309
|
+
|
|
310
|
+
const workspaceId = config.get('activeWorkspace');
|
|
311
|
+
if (!workspaceId) return error('No active workspace.');
|
|
312
|
+
|
|
313
|
+
const wsId = await resolveWorkspaceObjectId(workspaceId);
|
|
314
|
+
await withSpinner('Updating role...', () =>
|
|
315
|
+
client.put(`/workspace/${wsId}/member`, {
|
|
316
|
+
userId: options.user,
|
|
317
|
+
role: options.role,
|
|
318
|
+
})
|
|
319
|
+
);
|
|
320
|
+
success(`Member role updated to ${chalk.bold(options.role)}`);
|
|
321
|
+
} catch (err) {
|
|
322
|
+
error(`Failed to update role: ${err.message}`);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
152
326
|
module.exports = workspace;
|
package/src/index.js
CHANGED
|
@@ -16,7 +16,17 @@ program.addCommand(require('./commands/auth'));
|
|
|
16
16
|
program.addCommand(require('./commands/workspace'));
|
|
17
17
|
program.addCommand(require('./commands/board'));
|
|
18
18
|
program.addCommand(require('./commands/card'));
|
|
19
|
+
program.addCommand(require('./commands/list'));
|
|
20
|
+
program.addCommand(require('./commands/checklist'));
|
|
19
21
|
program.addCommand(require('./commands/page'));
|
|
22
|
+
program.addCommand(require('./commands/timer'));
|
|
23
|
+
program.addCommand(require('./commands/notification'));
|
|
24
|
+
program.addCommand(require('./commands/activity'));
|
|
25
|
+
program.addCommand(require('./commands/analytics'));
|
|
26
|
+
program.addCommand(require('./commands/automation'));
|
|
27
|
+
program.addCommand(require('./commands/webhook'));
|
|
28
|
+
program.addCommand(require('./commands/api-key'));
|
|
29
|
+
program.addCommand(require('./commands/import'));
|
|
20
30
|
program.addCommand(require('./commands/search'));
|
|
21
31
|
program.addCommand(require('./commands/ai'));
|
|
22
32
|
program.addCommand(require('./commands/status'));
|
package/src/lib/client.js
CHANGED
|
@@ -72,8 +72,48 @@ class ZoobbeClient {
|
|
|
72
72
|
return this.request('PATCH', path, body);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
async delete(path) {
|
|
76
|
-
return this.request('DELETE', path);
|
|
75
|
+
async delete(path, body) {
|
|
76
|
+
return this.request('DELETE', path, body);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async upload(path, filePath, fieldName = 'file') {
|
|
80
|
+
this.ensureAuth();
|
|
81
|
+
const fs = require('fs');
|
|
82
|
+
const nodePath = require('path');
|
|
83
|
+
|
|
84
|
+
const decoded = decodeURIComponent(path);
|
|
85
|
+
if (decoded.includes('..') || decoded.includes('//') || /\x00/.test(decoded)) {
|
|
86
|
+
throw new Error('Invalid request path');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const url = `${this.baseUrl}/v1${path}`;
|
|
90
|
+
const boundary = `----ZoobbeUpload${Date.now()}`;
|
|
91
|
+
const fileName = nodePath.basename(filePath).replace(/["\r\n\\]/g, '_');
|
|
92
|
+
const fileBuffer = fs.readFileSync(filePath);
|
|
93
|
+
|
|
94
|
+
const header = `--${boundary}\r\nContent-Disposition: form-data; name="${fieldName}"; filename="${fileName}"\r\nContent-Type: application/octet-stream\r\n\r\n`;
|
|
95
|
+
const footer = `\r\n--${boundary}--\r\n`;
|
|
96
|
+
|
|
97
|
+
const body = Buffer.concat([Buffer.from(header), fileBuffer, Buffer.from(footer)]);
|
|
98
|
+
|
|
99
|
+
const response = await fetch(url, {
|
|
100
|
+
method: 'POST',
|
|
101
|
+
headers: {
|
|
102
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
103
|
+
'User-Agent': 'Zoobbe-CLI/1.0',
|
|
104
|
+
'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
|
105
|
+
},
|
|
106
|
+
body,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const data = await response.json();
|
|
110
|
+
if (!response.ok) {
|
|
111
|
+
const msg = data.error || data.message || `Upload failed with status ${response.status}`;
|
|
112
|
+
const error = new Error(msg);
|
|
113
|
+
error.status = response.status;
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
return data;
|
|
77
117
|
}
|
|
78
118
|
}
|
|
79
119
|
|