tmux-team 1.0.0 → 2.0.0-alpha.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.
@@ -0,0 +1,654 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // PM Commands - project management CLI
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ import type { Context } from '../types.js';
6
+ import { ExitCodes } from '../exits.js';
7
+ import { colors } from '../ui.js';
8
+ import {
9
+ findCurrentTeamId,
10
+ getStorageAdapter,
11
+ generateTeamId,
12
+ getTeamsDir,
13
+ linkTeam,
14
+ listTeams,
15
+ createStorageAdapter,
16
+ saveTeamConfig,
17
+ } from './manager.js';
18
+ import type { StorageAdapter } from './storage/adapter.js';
19
+ import type { TaskStatus, MilestoneStatus, StorageBackend } from './types.js';
20
+ import path from 'path';
21
+
22
+ // ─────────────────────────────────────────────────────────────
23
+ // Helpers
24
+ // ─────────────────────────────────────────────────────────────
25
+
26
+ async function requireTeam(ctx: Context): Promise<{ teamId: string; storage: StorageAdapter }> {
27
+ const teamId = findCurrentTeamId(process.cwd(), ctx.paths.globalDir);
28
+ if (!teamId) {
29
+ ctx.ui.error("No team found. Run 'tmux-team pm init' first or navigate to a linked directory.");
30
+ ctx.exit(ExitCodes.CONFIG_MISSING);
31
+ }
32
+ const storage = getStorageAdapter(teamId, ctx.paths.globalDir);
33
+
34
+ // Validate team exists
35
+ const team = await storage.getTeam();
36
+ if (!team) {
37
+ ctx.ui.error(`Team ${teamId} not found. The .tmux-team-id file may be stale.`);
38
+ ctx.exit(ExitCodes.CONFIG_MISSING);
39
+ }
40
+
41
+ return { teamId, storage };
42
+ }
43
+
44
+ function formatStatus(status: TaskStatus | MilestoneStatus): string {
45
+ switch (status) {
46
+ case 'pending':
47
+ return colors.yellow('pending');
48
+ case 'in_progress':
49
+ return colors.blue('in_progress');
50
+ case 'done':
51
+ return colors.green('done');
52
+ default:
53
+ return status;
54
+ }
55
+ }
56
+
57
+ function parseStatus(s: string): TaskStatus {
58
+ const normalized = s.toLowerCase().replace(/-/g, '_');
59
+ if (normalized === 'pending' || normalized === 'in_progress' || normalized === 'done') {
60
+ return normalized as TaskStatus;
61
+ }
62
+ throw new Error(`Invalid status: ${s}. Use: pending, in_progress, done`);
63
+ }
64
+
65
+ // ─────────────────────────────────────────────────────────────
66
+ // Commands
67
+ // ─────────────────────────────────────────────────────────────
68
+
69
+ export async function cmdPmInit(ctx: Context, args: string[]): Promise<void> {
70
+ const { ui, flags, paths } = ctx;
71
+
72
+ // Parse flags: --name, --backend, --repo
73
+ let name = 'Unnamed Project';
74
+ let backend: StorageBackend = 'fs';
75
+ let repo: string | undefined;
76
+
77
+ for (let i = 0; i < args.length; i++) {
78
+ if (args[i] === '--name' && args[i + 1]) {
79
+ name = args[++i];
80
+ } else if (args[i].startsWith('--name=')) {
81
+ name = args[i].slice(7);
82
+ } else if (args[i] === '--backend' && args[i + 1]) {
83
+ const b = args[++i];
84
+ if (b !== 'fs' && b !== 'github') {
85
+ ui.error(`Invalid backend: ${b}. Use: fs, github`);
86
+ ctx.exit(ExitCodes.ERROR);
87
+ }
88
+ backend = b;
89
+ } else if (args[i].startsWith('--backend=')) {
90
+ const b = args[i].slice(10);
91
+ if (b !== 'fs' && b !== 'github') {
92
+ ui.error(`Invalid backend: ${b}. Use: fs, github`);
93
+ ctx.exit(ExitCodes.ERROR);
94
+ }
95
+ backend = b as StorageBackend;
96
+ } else if (args[i] === '--repo' && args[i + 1]) {
97
+ repo = args[++i];
98
+ } else if (args[i].startsWith('--repo=')) {
99
+ repo = args[i].slice(7);
100
+ }
101
+ }
102
+
103
+ // Validate GitHub backend requires repo
104
+ if (backend === 'github' && !repo) {
105
+ ui.error('GitHub backend requires --repo flag (e.g., --repo owner/repo)');
106
+ ctx.exit(ExitCodes.ERROR);
107
+ }
108
+
109
+ const teamId = generateTeamId();
110
+ const teamDir = path.join(getTeamsDir(paths.globalDir), teamId);
111
+
112
+ // Save config first
113
+ saveTeamConfig(teamDir, { backend, repo });
114
+
115
+ // Create storage adapter
116
+ const storage = createStorageAdapter(teamDir, backend, repo);
117
+
118
+ const team = await storage.initTeam(name);
119
+ linkTeam(process.cwd(), teamId);
120
+
121
+ await storage.appendEvent({
122
+ event: 'team_created',
123
+ id: teamId,
124
+ name,
125
+ backend,
126
+ repo,
127
+ actor: 'human',
128
+ ts: new Date().toISOString(),
129
+ });
130
+
131
+ if (flags.json) {
132
+ ui.json({ team, backend, repo, linked: process.cwd() });
133
+ } else {
134
+ ui.success(`Created team '${name}' (${teamId})`);
135
+ if (backend === 'github') {
136
+ ui.info(`Backend: GitHub (${repo})`);
137
+ }
138
+ ui.info(`Linked to ${process.cwd()}`);
139
+ }
140
+ }
141
+
142
+ export async function cmdPmMilestone(ctx: Context, args: string[]): Promise<void> {
143
+ const [subcommand, ...rest] = args;
144
+
145
+ switch (subcommand) {
146
+ case 'add':
147
+ return cmdMilestoneAdd(ctx, rest);
148
+ case 'list':
149
+ case 'ls':
150
+ return cmdMilestoneList(ctx, rest);
151
+ case 'done':
152
+ return cmdMilestoneDone(ctx, rest);
153
+ default:
154
+ ctx.ui.error(`Unknown milestone command: ${subcommand}. Use: add, list, done`);
155
+ ctx.exit(ExitCodes.ERROR);
156
+ }
157
+ }
158
+
159
+ async function cmdMilestoneAdd(ctx: Context, args: string[]): Promise<void> {
160
+ const { ui, flags } = ctx;
161
+ const { storage } = await requireTeam(ctx);
162
+
163
+ const name = args[0];
164
+ if (!name) {
165
+ ui.error('Usage: tmux-team pm milestone add <name>');
166
+ ctx.exit(ExitCodes.ERROR);
167
+ }
168
+
169
+ const milestone = await storage.createMilestone({ name });
170
+
171
+ await storage.appendEvent({
172
+ event: 'milestone_created',
173
+ id: milestone.id,
174
+ name,
175
+ actor: 'human',
176
+ ts: new Date().toISOString(),
177
+ });
178
+
179
+ if (flags.json) {
180
+ ui.json(milestone);
181
+ } else {
182
+ ui.success(`Created milestone #${milestone.id}: ${name}`);
183
+ }
184
+ }
185
+
186
+ async function cmdMilestoneList(ctx: Context, _args: string[]): Promise<void> {
187
+ const { ui, flags } = ctx;
188
+ const { storage } = await requireTeam(ctx);
189
+
190
+ const milestones = await storage.listMilestones();
191
+
192
+ if (flags.json) {
193
+ ui.json(milestones);
194
+ return;
195
+ }
196
+
197
+ if (milestones.length === 0) {
198
+ ui.info('No milestones. Use: tmux-team pm milestone add <name>');
199
+ return;
200
+ }
201
+
202
+ console.log();
203
+ ui.table(
204
+ ['ID', 'NAME', 'STATUS'],
205
+ milestones.map((m) => [m.id, m.name, formatStatus(m.status)])
206
+ );
207
+ console.log();
208
+ }
209
+
210
+ async function cmdMilestoneDone(ctx: Context, args: string[]): Promise<void> {
211
+ const { ui, flags } = ctx;
212
+ const { storage } = await requireTeam(ctx);
213
+
214
+ const id = args[0];
215
+ if (!id) {
216
+ ui.error('Usage: tmux-team pm milestone done <id>');
217
+ ctx.exit(ExitCodes.ERROR);
218
+ }
219
+
220
+ const milestone = await storage.getMilestone(id);
221
+ if (!milestone) {
222
+ ui.error(`Milestone ${id} not found`);
223
+ ctx.exit(ExitCodes.PANE_NOT_FOUND);
224
+ }
225
+
226
+ const updated = await storage.updateMilestone(id, { status: 'done' });
227
+
228
+ await storage.appendEvent({
229
+ event: 'milestone_updated',
230
+ id,
231
+ field: 'status',
232
+ from: milestone.status,
233
+ to: 'done',
234
+ actor: 'human',
235
+ ts: new Date().toISOString(),
236
+ });
237
+
238
+ if (flags.json) {
239
+ ui.json(updated);
240
+ } else {
241
+ ui.success(`Milestone #${id} marked as done`);
242
+ }
243
+ }
244
+
245
+ export async function cmdPmTask(ctx: Context, args: string[]): Promise<void> {
246
+ const [subcommand, ...rest] = args;
247
+
248
+ switch (subcommand) {
249
+ case 'add':
250
+ return cmdTaskAdd(ctx, rest);
251
+ case 'list':
252
+ case 'ls':
253
+ return cmdTaskList(ctx, rest);
254
+ case 'show':
255
+ return cmdTaskShow(ctx, rest);
256
+ case 'update':
257
+ return cmdTaskUpdate(ctx, rest);
258
+ case 'done':
259
+ return cmdTaskDone(ctx, rest);
260
+ default:
261
+ ctx.ui.error(`Unknown task command: ${subcommand}. Use: add, list, show, update, done`);
262
+ ctx.exit(ExitCodes.ERROR);
263
+ }
264
+ }
265
+
266
+ async function cmdTaskAdd(ctx: Context, args: string[]): Promise<void> {
267
+ const { ui, flags } = ctx;
268
+ const { storage } = await requireTeam(ctx);
269
+
270
+ // Parse args: <title> [--milestone <id>] [--assignee <name>]
271
+ let title = '';
272
+ let milestone: string | undefined;
273
+ let assignee: string | undefined;
274
+
275
+ for (let i = 0; i < args.length; i++) {
276
+ if (args[i] === '--milestone' || args[i] === '-m') {
277
+ milestone = args[++i];
278
+ } else if (args[i].startsWith('--milestone=')) {
279
+ milestone = args[i].slice(12);
280
+ } else if (args[i] === '--assignee' || args[i] === '-a') {
281
+ assignee = args[++i];
282
+ } else if (args[i].startsWith('--assignee=')) {
283
+ assignee = args[i].slice(11);
284
+ } else if (!title) {
285
+ title = args[i];
286
+ }
287
+ }
288
+
289
+ if (!title) {
290
+ ui.error('Usage: tmux-team pm task add <title> [--milestone <id>]');
291
+ ctx.exit(ExitCodes.ERROR);
292
+ }
293
+
294
+ const task = await storage.createTask({ title, milestone, assignee });
295
+
296
+ await storage.appendEvent({
297
+ event: 'task_created',
298
+ id: task.id,
299
+ title,
300
+ milestone,
301
+ actor: 'human',
302
+ ts: new Date().toISOString(),
303
+ });
304
+
305
+ if (flags.json) {
306
+ ui.json(task);
307
+ } else {
308
+ ui.success(`Created task #${task.id}: ${title}`);
309
+ }
310
+ }
311
+
312
+ async function cmdTaskList(ctx: Context, args: string[]): Promise<void> {
313
+ const { ui, flags } = ctx;
314
+ const { storage } = await requireTeam(ctx);
315
+
316
+ // Parse filters
317
+ let milestone: string | undefined;
318
+ let status: TaskStatus | undefined;
319
+
320
+ for (let i = 0; i < args.length; i++) {
321
+ if (args[i] === '--milestone' || args[i] === '-m') {
322
+ milestone = args[++i];
323
+ } else if (args[i].startsWith('--milestone=')) {
324
+ milestone = args[i].slice(12);
325
+ } else if (args[i] === '--status' || args[i] === '-s') {
326
+ status = parseStatus(args[++i]);
327
+ } else if (args[i].startsWith('--status=')) {
328
+ status = parseStatus(args[i].slice(9));
329
+ }
330
+ }
331
+
332
+ const tasks = await storage.listTasks({ milestone, status });
333
+
334
+ if (flags.json) {
335
+ ui.json(tasks);
336
+ return;
337
+ }
338
+
339
+ if (tasks.length === 0) {
340
+ ui.info('No tasks. Use: tmux-team pm task add <title>');
341
+ return;
342
+ }
343
+
344
+ console.log();
345
+ ui.table(
346
+ ['ID', 'TITLE', 'STATUS', 'MILESTONE'],
347
+ tasks.map((t) => [t.id, t.title.slice(0, 40), formatStatus(t.status), t.milestone || '-'])
348
+ );
349
+ console.log();
350
+ }
351
+
352
+ async function cmdTaskShow(ctx: Context, args: string[]): Promise<void> {
353
+ const { ui, flags } = ctx;
354
+ const { storage } = await requireTeam(ctx);
355
+
356
+ const id = args[0];
357
+ if (!id) {
358
+ ui.error('Usage: tmux-team pm task show <id>');
359
+ ctx.exit(ExitCodes.ERROR);
360
+ }
361
+
362
+ const task = await storage.getTask(id);
363
+ if (!task) {
364
+ ui.error(`Task ${id} not found`);
365
+ ctx.exit(ExitCodes.PANE_NOT_FOUND);
366
+ }
367
+
368
+ if (flags.json) {
369
+ ui.json(task);
370
+ return;
371
+ }
372
+
373
+ console.log();
374
+ console.log(colors.cyan(`Task #${task.id}: ${task.title}`));
375
+ console.log(`Status: ${formatStatus(task.status)}`);
376
+ if (task.milestone) console.log(`Milestone: #${task.milestone}`);
377
+ if (task.assignee) console.log(`Assignee: ${task.assignee}`);
378
+ console.log(`Created: ${task.createdAt}`);
379
+ console.log(`Updated: ${task.updatedAt}`);
380
+ console.log();
381
+ }
382
+
383
+ async function cmdTaskUpdate(ctx: Context, args: string[]): Promise<void> {
384
+ const { ui, flags } = ctx;
385
+ const { storage } = await requireTeam(ctx);
386
+
387
+ // Parse: <id> --status <status> [--assignee <name>]
388
+ const id = args[0];
389
+ if (!id) {
390
+ ui.error('Usage: tmux-team pm task update <id> --status <status>');
391
+ ctx.exit(ExitCodes.ERROR);
392
+ }
393
+
394
+ let status: TaskStatus | undefined;
395
+ let assignee: string | undefined;
396
+
397
+ for (let i = 1; i < args.length; i++) {
398
+ if (args[i] === '--status' || args[i] === '-s') {
399
+ status = parseStatus(args[++i]);
400
+ } else if (args[i].startsWith('--status=')) {
401
+ status = parseStatus(args[i].slice(9));
402
+ } else if (args[i] === '--assignee' || args[i] === '-a') {
403
+ assignee = args[++i];
404
+ } else if (args[i].startsWith('--assignee=')) {
405
+ assignee = args[i].slice(11);
406
+ }
407
+ }
408
+
409
+ const task = await storage.getTask(id);
410
+ if (!task) {
411
+ ui.error(`Task ${id} not found`);
412
+ ctx.exit(ExitCodes.PANE_NOT_FOUND);
413
+ }
414
+
415
+ const updates: { status?: TaskStatus; assignee?: string } = {};
416
+ if (status) updates.status = status;
417
+ if (assignee) updates.assignee = assignee;
418
+
419
+ if (Object.keys(updates).length === 0) {
420
+ ui.error('No updates specified. Use --status or --assignee');
421
+ ctx.exit(ExitCodes.ERROR);
422
+ }
423
+
424
+ const updated = await storage.updateTask(id, updates);
425
+
426
+ if (status) {
427
+ await storage.appendEvent({
428
+ event: 'task_updated',
429
+ id,
430
+ field: 'status',
431
+ from: task.status,
432
+ to: status,
433
+ actor: 'human',
434
+ ts: new Date().toISOString(),
435
+ });
436
+ }
437
+
438
+ if (flags.json) {
439
+ ui.json(updated);
440
+ } else {
441
+ ui.success(`Updated task #${id}`);
442
+ }
443
+ }
444
+
445
+ async function cmdTaskDone(ctx: Context, args: string[]): Promise<void> {
446
+ const { ui, flags } = ctx;
447
+ const { storage } = await requireTeam(ctx);
448
+
449
+ const id = args[0];
450
+ if (!id) {
451
+ ui.error('Usage: tmux-team pm task done <id>');
452
+ ctx.exit(ExitCodes.ERROR);
453
+ }
454
+
455
+ const task = await storage.getTask(id);
456
+ if (!task) {
457
+ ui.error(`Task ${id} not found`);
458
+ ctx.exit(ExitCodes.PANE_NOT_FOUND);
459
+ }
460
+
461
+ const updated = await storage.updateTask(id, { status: 'done' });
462
+
463
+ await storage.appendEvent({
464
+ event: 'task_updated',
465
+ id,
466
+ field: 'status',
467
+ from: task.status,
468
+ to: 'done',
469
+ actor: 'human',
470
+ ts: new Date().toISOString(),
471
+ });
472
+
473
+ if (flags.json) {
474
+ ui.json(updated);
475
+ } else {
476
+ ui.success(`Task #${id} marked as done`);
477
+ }
478
+ }
479
+
480
+ export async function cmdPmDoc(ctx: Context, args: string[]): Promise<void> {
481
+ const { ui, flags } = ctx;
482
+ const { teamId, storage } = await requireTeam(ctx);
483
+
484
+ const id = args[0];
485
+ if (!id) {
486
+ ui.error('Usage: tmux-team pm doc <id> [--print]');
487
+ ctx.exit(ExitCodes.ERROR);
488
+ }
489
+
490
+ const task = await storage.getTask(id);
491
+ if (!task) {
492
+ ui.error(`Task ${id} not found`);
493
+ ctx.exit(ExitCodes.PANE_NOT_FOUND);
494
+ }
495
+
496
+ const printOnly = args.includes('--print') || args.includes('-p');
497
+ const doc = await storage.getTaskDoc(id);
498
+
499
+ if (printOnly || flags.json) {
500
+ if (flags.json) {
501
+ ui.json({ id, doc });
502
+ } else {
503
+ console.log(doc || '(empty)');
504
+ }
505
+ return;
506
+ }
507
+
508
+ // Open in editor
509
+ const editor = process.env.EDITOR || 'vim';
510
+ const docPath = path.join(getTeamsDir(ctx.paths.globalDir), teamId, 'tasks', `${id}.md`);
511
+
512
+ const { spawnSync } = await import('child_process');
513
+ spawnSync(editor, [docPath], { stdio: 'inherit' });
514
+
515
+ ui.success(`Saved documentation for task #${id}`);
516
+ }
517
+
518
+ export async function cmdPmLog(ctx: Context, args: string[]): Promise<void> {
519
+ const { ui, flags } = ctx;
520
+ const { storage } = await requireTeam(ctx);
521
+
522
+ // Parse --limit flag
523
+ let limit: number | undefined;
524
+ for (let i = 0; i < args.length; i++) {
525
+ if (args[i] === '--limit' || args[i] === '-n') {
526
+ limit = parseInt(args[++i], 10);
527
+ } else if (args[i].startsWith('--limit=')) {
528
+ limit = parseInt(args[i].slice(8), 10);
529
+ }
530
+ }
531
+
532
+ const events = await storage.getEvents(limit);
533
+
534
+ if (flags.json) {
535
+ ui.json(events);
536
+ return;
537
+ }
538
+
539
+ if (events.length === 0) {
540
+ ui.info('No events logged yet.');
541
+ return;
542
+ }
543
+
544
+ console.log();
545
+ for (const event of events) {
546
+ const time = event.ts.slice(0, 19).replace('T', ' ');
547
+ const actor = colors.cyan(event.actor);
548
+ const action = colors.yellow(event.event);
549
+ const id = event.id ? `#${event.id}` : '';
550
+ console.log(`${colors.dim(time)} ${actor} ${action} ${id}`);
551
+ }
552
+ console.log();
553
+ }
554
+
555
+ export async function cmdPmList(ctx: Context, _args: string[]): Promise<void> {
556
+ const { ui, flags, paths } = ctx;
557
+
558
+ const teams = listTeams(paths.globalDir);
559
+
560
+ if (flags.json) {
561
+ ui.json(teams);
562
+ return;
563
+ }
564
+
565
+ if (teams.length === 0) {
566
+ ui.info("No teams. Use: tmux-team pm init --name 'My Project'");
567
+ return;
568
+ }
569
+
570
+ console.log();
571
+ ui.table(
572
+ ['ID', 'NAME', 'CREATED'],
573
+ teams.map((t) => [t.id.slice(0, 8) + '...', t.name, t.createdAt.slice(0, 10)])
574
+ );
575
+ console.log();
576
+ }
577
+
578
+ // ─────────────────────────────────────────────────────────────
579
+ // Main PM router
580
+ // ─────────────────────────────────────────────────────────────
581
+
582
+ export async function cmdPm(ctx: Context, args: string[]): Promise<void> {
583
+ const [subcommand, ...rest] = args;
584
+
585
+ // Handle shorthands
586
+ const cmd = subcommand === 'm' ? 'milestone' : subcommand === 't' ? 'task' : subcommand;
587
+
588
+ switch (cmd) {
589
+ case 'init':
590
+ return cmdPmInit(ctx, rest);
591
+ case 'milestone':
592
+ return cmdPmMilestone(ctx, rest);
593
+ case 'task':
594
+ return cmdPmTask(ctx, rest);
595
+ case 'doc':
596
+ return cmdPmDoc(ctx, rest);
597
+ case 'log':
598
+ return cmdPmLog(ctx, rest);
599
+ case 'list':
600
+ case 'ls':
601
+ return cmdPmList(ctx, rest);
602
+ case undefined:
603
+ case 'help':
604
+ return cmdPmHelp(ctx);
605
+ default:
606
+ ctx.ui.error(`Unknown pm command: ${subcommand}. Run 'tmux-team pm help'`);
607
+ ctx.exit(ExitCodes.ERROR);
608
+ }
609
+ }
610
+
611
+ function cmdPmHelp(_ctx: Context): void {
612
+ console.log(`
613
+ ${colors.cyan('tmux-team pm')} - Project management
614
+
615
+ ${colors.yellow('COMMANDS')}
616
+ ${colors.green('init')} [options] Create a new team/project
617
+ --name <name> Project name
618
+ --backend <fs|github> Storage backend (default: fs)
619
+ --repo <owner/repo> GitHub repo (required for github backend)
620
+ ${colors.green('list')} List all teams
621
+ ${colors.green('milestone')} add <name> Add milestone (shorthand: m)
622
+ ${colors.green('milestone')} list List milestones
623
+ ${colors.green('milestone')} done <id> Mark milestone complete
624
+ ${colors.green('task')} add <title> [--milestone] Add task (shorthand: t)
625
+ ${colors.green('task')} list [--status] [--milestone] List tasks
626
+ ${colors.green('task')} show <id> Show task details
627
+ ${colors.green('task')} update <id> --status <s> Update task status
628
+ ${colors.green('task')} done <id> Mark task complete
629
+ ${colors.green('doc')} <id> [--print] View/edit task documentation
630
+ ${colors.green('log')} [--limit <n>] Show audit event log
631
+
632
+ ${colors.yellow('BACKENDS')}
633
+ ${colors.cyan('fs')} Local filesystem (default) - tasks in ~/.config/tmux-team/teams/
634
+ ${colors.cyan('github')} GitHub Issues - tasks become issues, milestones sync with GH
635
+
636
+ ${colors.yellow('SHORTHANDS')}
637
+ pm m = pm milestone
638
+ pm t = pm task
639
+ pm ls = pm list
640
+
641
+ ${colors.yellow('EXAMPLES')}
642
+ # Local filesystem backend (default)
643
+ tmux-team pm init --name "Auth Refactor"
644
+
645
+ # GitHub backend - uses gh CLI for auth
646
+ tmux-team pm init --name "Sprint 1" --backend github --repo owner/repo
647
+
648
+ tmux-team pm m add "Phase 1"
649
+ tmux-team pm t add "Implement login" --milestone 1
650
+ tmux-team pm t list --status pending
651
+ tmux-team pm t done 1
652
+ tmux-team pm log --limit 10
653
+ `);
654
+ }