claude-mpm 4.1.22__py3-none-any.whl → 4.1.24__py3-none-any.whl

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.
@@ -134,6 +134,8 @@ class UnifiedDataViewer {
134
134
 
135
135
  /**
136
136
  * Display event data with comprehensive formatting
137
+ * PRIMARY: Event type, timestamp, and key details
138
+ * SECONDARY: Full event data in collapsible JSON
137
139
  */
138
140
  displayEvent(data) {
139
141
  const eventType = this.formatEventType(data);
@@ -147,19 +149,45 @@ class UnifiedDataViewer {
147
149
  <div class="unified-viewer-content">
148
150
  `;
149
151
 
150
- // Event-specific details
152
+ // PRIMARY DATA: Event-specific key details
153
+ html += `<div class="primary-data">`;
151
154
  html += this.formatEventDetails(data);
152
-
153
- // Tool parameters if present
154
- if (data.tool_parameters || (data.data && data.data.tool_parameters)) {
155
- const params = data.tool_parameters || data.data.tool_parameters;
156
- html += this.formatParameters(params, 'Tool Parameters');
155
+
156
+ // Show important tool parameters inline if present
157
+ if (data.tool_name || data.data?.tool_name) {
158
+ const toolName = data.tool_name || data.data.tool_name;
159
+ html += `
160
+ <div class="detail-row highlight">
161
+ <span class="detail-label">Tool:</span>
162
+ <span class="detail-value">${this.getToolIcon(toolName)} ${toolName}</span>
163
+ </div>
164
+ `;
165
+
166
+ // Show key parameters for specific tools
167
+ const params = data.tool_parameters || data.data?.tool_parameters;
168
+ if (params) {
169
+ if (params.file_path) {
170
+ html += `
171
+ <div class="detail-row">
172
+ <span class="detail-label">File:</span>
173
+ <span class="detail-value code">${params.file_path}</span>
174
+ </div>
175
+ `;
176
+ }
177
+ if (params.command) {
178
+ html += `
179
+ <div class="detail-row">
180
+ <span class="detail-label">Command:</span>
181
+ <pre class="code-snippet">${this.escapeHtml(params.command)}</pre>
182
+ </div>
183
+ `;
184
+ }
185
+ }
157
186
  }
187
+ html += `</div>`;
158
188
 
159
- // Event data if present
160
- if (data.data && Object.keys(data.data).length > 0) {
161
- html += this.formatEventData(data);
162
- }
189
+ // SECONDARY DATA: Collapsible JSON viewer for full event data
190
+ html += this.createCollapsibleJSON(data, 'Full Event Data');
163
191
 
164
192
  html += '</div>';
165
193
  this.container.innerHTML = html;
@@ -167,212 +195,341 @@ class UnifiedDataViewer {
167
195
 
168
196
  /**
169
197
  * Display agent data with full details
198
+ * PRIMARY: Agent status, active tools, and key info
199
+ * SECONDARY: Full agent data in collapsible JSON
170
200
  */
171
201
  displayAgent(data) {
172
202
  const agentIcon = this.getAgentIcon(data.name || data.agentName);
203
+ const agentName = data.name || data.agentName || 'Unknown Agent';
173
204
  const status = this.formatStatus(data.status);
174
205
 
175
206
  let html = `
176
207
  <div class="unified-viewer-header">
177
- <h6>${agentIcon} ${data.name || data.agentName || 'Unknown Agent'}</h6>
208
+ <h6>${agentIcon} ${agentName}</h6>
178
209
  <span class="unified-viewer-status">${status}</span>
179
210
  </div>
180
211
  <div class="unified-viewer-content">
181
- <div class="detail-row">
182
- <span class="detail-label">Status:</span>
183
- <span class="detail-value">${status}</span>
184
- </div>
185
- <div class="detail-row">
186
- <span class="detail-label">Session ID:</span>
187
- <span class="detail-value">${data.sessionId || data.session_id || 'N/A'}</span>
188
- </div>
189
- <div class="detail-row">
190
- <span class="detail-label">Timestamp:</span>
191
- <span class="detail-value">${this.formatTimestamp(data.timestamp)}</span>
192
- </div>
193
212
  `;
194
213
 
195
- // Tools used by agent
214
+ // PRIMARY DATA: Key agent information
215
+ html += `<div class="primary-data">`;
216
+
217
+ // Status with visual indicator
218
+ html += `
219
+ <div class="detail-row highlight">
220
+ <span class="detail-label">Status:</span>
221
+ <span class="detail-value ${this.formatStatusClass(status)}">${status}</span>
222
+ </div>
223
+ `;
224
+
225
+ // Tools summary if present
196
226
  if (data.tools && data.tools.length > 0) {
227
+ // Show active tools prominently
228
+ const activeTools = data.tools.filter(t => t.status === 'in_progress');
229
+ const completedTools = data.tools.filter(t => t.status === 'completed');
230
+
231
+ if (activeTools.length > 0) {
232
+ html += `
233
+ <div class="active-tools-section">
234
+ <span class="section-label">🔄 Active Tools:</span>
235
+ <div class="tools-grid">
236
+ `;
237
+ activeTools.forEach(tool => {
238
+ html += `
239
+ <div class="tool-chip active">
240
+ ${this.getToolIcon(tool.name)} ${tool.name}
241
+ </div>
242
+ `;
243
+ });
244
+ html += `</div></div>`;
245
+ }
246
+
197
247
  html += `
198
- <div class="detail-section">
199
- <span class="detail-section-title">Tools Used (${data.tools.length}):</span>
200
- <div class="tools-list">
201
- ${data.tools.map(tool => `
202
- <div class="tool-summary">
203
- <span class="tool-icon">${this.getToolIcon(tool.name)}</span>
204
- <span class="tool-name">${tool.name}</span>
205
- <span class="tool-status ${this.formatStatusClass(tool.status)}">${tool.status}</span>
206
- </div>
207
- `).join('')}
208
- </div>
248
+ <div class="detail-row">
249
+ <span class="detail-label">Tools Summary:</span>
250
+ <span class="detail-value">
251
+ ${activeTools.length} active, ${completedTools.length} completed, ${data.tools.length} total
252
+ </span>
209
253
  </div>
210
254
  `;
211
255
  }
212
256
 
257
+ // Current task if available
258
+ if (data.currentTask || data.description) {
259
+ html += `
260
+ <div class="detail-row">
261
+ <span class="detail-label">Current Task:</span>
262
+ <span class="detail-value">${data.currentTask || data.description}</span>
263
+ </div>
264
+ `;
265
+ }
266
+
267
+ html += `</div>`;
268
+
269
+ // SECONDARY DATA: Collapsible JSON viewer
270
+ html += this.createCollapsibleJSON(data, 'Full Agent Details');
271
+
213
272
  html += '</div>';
214
273
  this.container.innerHTML = html;
215
274
  }
216
275
 
217
276
  /**
218
277
  * Display tool data with parameters and results
278
+ * Special handling for TodoWrite to show todos prominently
219
279
  */
220
280
  displayTool(data) {
221
- const toolIcon = this.getToolIcon(data.name || data.tool_name);
281
+ const toolName = data.name || data.tool_name || 'Unknown Tool';
282
+ const toolIcon = this.getToolIcon(toolName);
222
283
  const status = this.formatStatus(data.status);
223
284
 
285
+ // Special handling for TodoWrite tool
286
+ if (toolName === 'TodoWrite') {
287
+ this.displayTodoWriteTool(data);
288
+ return;
289
+ }
290
+
224
291
  let html = `
225
292
  <div class="unified-viewer-header">
226
- <h6>${toolIcon} ${data.name || data.tool_name || 'Unknown Tool'}</h6>
293
+ <h6>${toolIcon} ${toolName}</h6>
227
294
  <span class="unified-viewer-status">${status}</span>
228
295
  </div>
229
296
  <div class="unified-viewer-content">
297
+ `;
298
+
299
+ // PRIMARY DATA: Show important tool-specific information first
300
+ const params = data.params || data.tool_parameters || {};
301
+
302
+ // Tool-specific primary data display
303
+ if (toolName === 'Read' || toolName === 'Edit' || toolName === 'Write') {
304
+ // File tools - show file path prominently
305
+ if (params.file_path) {
306
+ html += `
307
+ <div class="primary-data">
308
+ <div class="detail-row highlight">
309
+ <span class="detail-label">📁 File:</span>
310
+ <span class="detail-value code">${params.file_path}</span>
311
+ </div>
312
+ `;
313
+ if (params.old_string) {
314
+ html += `
315
+ <div class="detail-row">
316
+ <span class="detail-label">Old Text:</span>
317
+ <pre class="code-snippet">${this.escapeHtml(params.old_string.substring(0, 200))}${params.old_string.length > 200 ? '...' : ''}</pre>
318
+ </div>
319
+ `;
320
+ }
321
+ if (params.new_string) {
322
+ html += `
323
+ <div class="detail-row">
324
+ <span class="detail-label">New Text:</span>
325
+ <pre class="code-snippet">${this.escapeHtml(params.new_string.substring(0, 200))}${params.new_string.length > 200 ? '...' : ''}</pre>
326
+ </div>
327
+ `;
328
+ }
329
+ html += '</div>';
330
+ }
331
+ } else if (toolName === 'Bash') {
332
+ // Bash tool - show command prominently
333
+ if (params.command) {
334
+ html += `
335
+ <div class="primary-data">
336
+ <div class="detail-row highlight">
337
+ <span class="detail-label">💻 Command:</span>
338
+ <pre class="code-snippet">${this.escapeHtml(params.command)}</pre>
339
+ </div>
340
+ </div>
341
+ `;
342
+ }
343
+ } else if (toolName === 'Grep' || toolName === 'Glob') {
344
+ // Search tools - show pattern prominently
345
+ if (params.pattern) {
346
+ html += `
347
+ <div class="primary-data">
348
+ <div class="detail-row highlight">
349
+ <span class="detail-label">🔍 Pattern:</span>
350
+ <span class="detail-value code">${this.escapeHtml(params.pattern)}</span>
351
+ </div>
352
+ `;
353
+ if (params.path) {
354
+ html += `
355
+ <div class="detail-row">
356
+ <span class="detail-label">Path:</span>
357
+ <span class="detail-value">${params.path}</span>
358
+ </div>
359
+ `;
360
+ }
361
+ html += '</div>';
362
+ }
363
+ } else if (toolName === 'Task') {
364
+ // Task tool - show delegation info prominently
365
+ if (params.subagent_type) {
366
+ html += `
367
+ <div class="primary-data">
368
+ <div class="detail-row highlight">
369
+ <span class="detail-label">🤖 Delegating to:</span>
370
+ <span class="detail-value">${params.subagent_type} agent</span>
371
+ </div>
372
+ `;
373
+ if (params.description) {
374
+ html += `
375
+ <div class="detail-row">
376
+ <span class="detail-label">Task:</span>
377
+ <span class="detail-value">${params.description}</span>
378
+ </div>
379
+ `;
380
+ }
381
+ html += '</div>';
382
+ }
383
+ }
384
+
385
+ // Status and metadata
386
+ html += `
387
+ <div class="detail-row">
388
+ <span class="detail-label">Status:</span>
389
+ <span class="detail-value">${status}</span>
390
+ </div>
391
+ `;
392
+
393
+ if (data.callCount) {
394
+ html += `
230
395
  <div class="detail-row">
231
- <span class="detail-label">Type:</span>
232
- <span class="detail-value">Tool</span>
233
- </div>
234
- <div class="detail-row">
235
- <span class="detail-label">Name:</span>
236
- <span class="detail-value">${data.name || data.tool_name || 'Unknown Tool'}</span>
237
- </div>
238
- <div class="detail-row">
239
- <span class="detail-label">Status:</span>
240
- <span class="detail-value">${status}</span>
396
+ <span class="detail-label">Call Count:</span>
397
+ <span class="detail-value">${data.callCount}</span>
241
398
  </div>
399
+ `;
400
+ }
401
+
402
+ // Collapsible JSON viewer for full details
403
+ html += this.createCollapsibleJSON(data, 'Full Tool Details');
404
+
405
+ html += '</div>';
406
+ this.container.innerHTML = html;
407
+ }
408
+
409
+ /**
410
+ * Display TodoWrite tool with todos list prominently after title
411
+ */
412
+ displayTodoWriteTool(data) {
413
+ const status = this.formatStatus(data.status);
414
+ const params = data.params || data.tool_parameters || {};
415
+ const todos = params.todos || [];
416
+
417
+ let html = `
418
+ <div class="unified-viewer-header">
419
+ <h6>📝 TodoWrite</h6>
420
+ <span class="unified-viewer-status">${status}</span>
421
+ </div>
422
+ <div class="unified-viewer-content">
242
423
  `;
243
424
 
244
- // Tool parameters
245
- if (data.params || data.tool_parameters) {
246
- const params = data.params || data.tool_parameters;
247
- html += this.formatParameters(params, 'Parameters');
248
- }
425
+ // PRIMARY DATA: Todo list and status summary immediately after title
426
+ if (todos.length > 0) {
427
+ const statusCounts = this.getTodoStatusCounts(todos);
428
+
429
+ // Status summary - horizontal single line format
430
+ html += `
431
+ <div class="todo-status-line">
432
+ <span class="status-inline">✅ ${statusCounts.completed} Done</span>
433
+ <span class="status-inline">🔄 ${statusCounts.in_progress} Active</span>
434
+ <span class="status-inline">⏳ ${statusCounts.pending} Pending</span>
435
+ </div>
436
+ `;
249
437
 
250
- // Timestamp
251
- if (data.timestamp) {
438
+ // Todo items list
439
+ html += `
440
+ <div class="todo-list-primary">
441
+ `;
442
+
443
+ todos.forEach((todo, index) => {
444
+ const statusIcon = this.getCheckboxIcon(todo.status);
445
+ const displayText = todo.status === 'in_progress' ?
446
+ (todo.activeForm || todo.content) : todo.content;
447
+ const statusClass = this.formatStatusClass(todo.status);
448
+
449
+ html += `
450
+ <div class="todo-item ${todo.status}">
451
+ <span class="todo-icon ${statusClass}">${statusIcon}</span>
452
+ <span class="todo-text">${this.escapeHtml(displayText)}</span>
453
+ ${todo.status === 'in_progress' ? '<span class="todo-badge active">ACTIVE</span>' : ''}
454
+ </div>
455
+ `;
456
+ });
457
+
458
+ html += `
459
+ </div>
460
+ `;
461
+ } else {
252
462
  html += `
253
463
  <div class="detail-row">
254
- <span class="detail-label">Timestamp:</span>
255
- <span class="detail-value">${this.formatTimestamp(data.timestamp)}</span>
464
+ <span class="detail-value">No todos in list</span>
256
465
  </div>
257
466
  `;
258
467
  }
259
468
 
260
- // Tool result
261
- if (data.result) {
469
+ // Metadata section
470
+ if (data.callCount && data.callCount > 1) {
262
471
  html += `
263
- <div class="detail-section">
264
- <span class="detail-section-title">Result:</span>
265
- <pre class="tool-result">${this.escapeHtml(JSON.stringify(data.result, null, 2))}</pre>
472
+ <div class="detail-row">
473
+ <span class="detail-label">Updates:</span>
474
+ <span class="detail-value">${data.callCount}</span>
266
475
  </div>
267
476
  `;
268
477
  }
269
478
 
479
+ // Collapsible JSON viewer for full details
480
+ html += this.createCollapsibleJSON(data, 'Full Details');
481
+
270
482
  html += '</div>';
271
483
  this.container.innerHTML = html;
272
484
  }
273
485
 
274
486
  /**
275
- * Display todo data with checklist formatting
487
+ * Display todo data with checklist formatting (for standalone todos, not TodoWrite)
276
488
  */
277
489
  displayTodo(data) {
278
- // Handle different data structures for TodoWrite
490
+ // Handle different data structures for standalone todos
279
491
  let todos;
280
492
  let toolName = 'Todo List';
281
- let timestamp = null;
282
- let status = null;
283
493
 
284
494
  if (data.todos && Array.isArray(data.todos)) {
285
- // Direct todo list format
286
495
  todos = data.todos;
287
- } else if (data.tool_parameters && data.tool_parameters.todos) {
288
- // TodoWrite tool format
289
- todos = data.tool_parameters.todos;
290
- toolName = 'TodoWrite';
291
- timestamp = data.timestamp;
292
- status = data.status;
293
496
  } else if (Array.isArray(data)) {
294
- // Array of todos
295
497
  todos = data;
296
498
  } else if (data.content && data.activeForm && data.status) {
297
- // Single todo item
298
499
  todos = [data];
299
500
  } else {
300
- // Fallback
301
501
  todos = [];
302
502
  }
303
503
 
304
504
  let html = `
305
505
  <div class="unified-viewer-header">
306
- <h6>📝 ${toolName}</h6>
307
- ${status ? `<span class="unified-viewer-status">${this.formatStatus(status)}</span>` : ''}
506
+ <h6>📋 ${toolName}</h6>
308
507
  </div>
309
508
  <div class="unified-viewer-content">
310
509
  `;
311
510
 
312
- // Show timestamp if available
313
- if (timestamp) {
314
- html += `
315
- <div class="detail-row">
316
- <span class="detail-label">Timestamp:</span>
317
- <span class="detail-value">${this.formatTimestamp(timestamp)}</span>
318
- </div>
319
- `;
320
- }
321
-
322
511
  if (todos.length > 0) {
323
- // Status summary with enhanced formatting
324
- const statusCounts = this.getTodoStatusCounts(todos);
325
- html += `
326
- <div class="detail-section">
327
- <span class="detail-section-title">Todo Summary</span>
328
- <div class="todo-summary">
329
- <div class="summary-item completed">
330
- <span class="summary-icon">✅</span>
331
- <span class="summary-count">${statusCounts.completed}</span>
332
- <span class="summary-label">Completed</span>
333
- </div>
334
- <div class="summary-item in_progress">
335
- <span class="summary-icon">🔄</span>
336
- <span class="summary-count">${statusCounts.in_progress}</span>
337
- <span class="summary-label">In Progress</span>
338
- </div>
339
- <div class="summary-item pending">
340
- <span class="summary-icon">⏳</span>
341
- <span class="summary-count">${statusCounts.pending}</span>
342
- <span class="summary-label">Pending</span>
343
- </div>
344
- </div>
345
- </div>
346
- `;
347
-
348
- // Enhanced todo items display
512
+ // Show todos immediately
349
513
  html += `
350
- <div class="detail-section">
351
- <span class="detail-section-title">Todo List (${todos.length} items)</span>
352
- <div class="todo-checklist">
514
+ <div class="todo-list-primary">
353
515
  `;
354
516
 
355
- todos.forEach((todo, index) => {
517
+ todos.forEach((todo) => {
356
518
  const statusIcon = this.getCheckboxIcon(todo.status);
357
519
  const displayText = todo.status === 'in_progress' ?
358
520
  (todo.activeForm || todo.content) : todo.content;
359
521
  const statusClass = this.formatStatusClass(todo.status);
360
522
 
361
523
  html += `
362
- <div class="todo-checklist-item ${todo.status}">
363
- <div class="todo-checkbox">
364
- <span class="checkbox-icon ${statusClass}">${statusIcon}</span>
365
- </div>
366
- <div class="todo-text">
367
- <span class="todo-content">${this.escapeHtml(displayText)}</span>
368
- <span class="todo-status-badge ${statusClass}">${todo.status.replace('_', ' ')}</span>
369
- </div>
524
+ <div class="todo-item ${todo.status}">
525
+ <span class="todo-icon ${statusClass}">${statusIcon}</span>
526
+ <span class="todo-text">${this.escapeHtml(displayText)}</span>
527
+ <span class="todo-status-text ${statusClass}">${todo.status.replace('_', ' ')}</span>
370
528
  </div>
371
529
  `;
372
530
  });
373
531
 
374
532
  html += `
375
- </div>
376
533
  </div>
377
534
  `;
378
535
  } else {
@@ -389,6 +546,8 @@ class UnifiedDataViewer {
389
546
 
390
547
  /**
391
548
  * Display instruction data
549
+ * PRIMARY: Instruction text prominently displayed
550
+ * SECONDARY: Metadata in collapsible section
392
551
  */
393
552
  displayInstruction(data) {
394
553
  let html = `
@@ -397,16 +556,27 @@ class UnifiedDataViewer {
397
556
  <span class="unified-viewer-timestamp">${this.formatTimestamp(data.timestamp)}</span>
398
557
  </div>
399
558
  <div class="unified-viewer-content">
400
- <div class="detail-row">
401
- <span class="detail-label">Content:</span>
402
- <div class="detail-value instruction-text">${this.escapeHtml(data.text)}</div>
559
+ `;
560
+
561
+ // PRIMARY DATA: The instruction text itself
562
+ html += `
563
+ <div class="primary-data">
564
+ <div class="instruction-content">
565
+ ${this.escapeHtml(data.text)}
403
566
  </div>
404
- <div class="detail-row">
405
- <span class="detail-label">Length:</span>
406
- <span class="detail-value">${data.text.length} characters</span>
567
+ <div class="instruction-meta">
568
+ <span class="meta-item">📏 ${data.text.length} characters</span>
569
+ <span class="meta-item">🕐 ${this.formatTimestamp(data.timestamp)}</span>
407
570
  </div>
408
571
  </div>
409
572
  `;
573
+
574
+ // SECONDARY DATA: Full instruction object if there's more data
575
+ if (Object.keys(data).length > 3) {
576
+ html += this.createCollapsibleJSON(data, 'Full Instruction Data');
577
+ }
578
+
579
+ html += '</div>';
410
580
  this.container.innerHTML = html;
411
581
  }
412
582
 
@@ -1023,6 +1193,69 @@ class UnifiedDataViewer {
1023
1193
  return div.innerHTML;
1024
1194
  }
1025
1195
 
1196
+ /**
1197
+ * Create a collapsible JSON viewer for secondary details
1198
+ * Provides a clean way to show full data without cluttering the main view
1199
+ */
1200
+ createCollapsibleJSON(data, title = 'Full Details') {
1201
+ // Generate unique ID for this collapsible section
1202
+ const sectionId = `json-details-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
1203
+
1204
+ // Filter out sensitive or overly verbose properties
1205
+ const cleanData = this.cleanDataForDisplay(data);
1206
+
1207
+ return `
1208
+ <div class="collapsible-json-section">
1209
+ <button class="collapsible-json-toggle" onclick="
1210
+ const content = document.getElementById('${sectionId}');
1211
+ const button = this;
1212
+ if (content.style.display === 'none' || content.style.display === '') {
1213
+ content.style.display = 'block';
1214
+ button.classList.add('expanded');
1215
+ button.innerHTML = '▼ ${title}';
1216
+ } else {
1217
+ content.style.display = 'none';
1218
+ button.classList.remove('expanded');
1219
+ button.innerHTML = '▶ ${title}';
1220
+ }
1221
+ ">▶ ${title}</button>
1222
+ <div id="${sectionId}" class="collapsible-json-content" style="display: none;">
1223
+ <pre class="json-viewer">${this.escapeHtml(JSON.stringify(cleanData, null, 2))}</pre>
1224
+ </div>
1225
+ </div>
1226
+ `;
1227
+ }
1228
+
1229
+ /**
1230
+ * Clean data for display in JSON viewer
1231
+ * Removes circular references and limits string lengths
1232
+ */
1233
+ cleanDataForDisplay(data) {
1234
+ const seen = new WeakSet();
1235
+
1236
+ return JSON.parse(JSON.stringify(data, (key, value) => {
1237
+ // Handle circular references
1238
+ if (typeof value === 'object' && value !== null) {
1239
+ if (seen.has(value)) {
1240
+ return '[Circular Reference]';
1241
+ }
1242
+ seen.add(value);
1243
+ }
1244
+
1245
+ // Truncate very long strings
1246
+ if (typeof value === 'string' && value.length > 1000) {
1247
+ return value.substring(0, 1000) + '... [truncated]';
1248
+ }
1249
+
1250
+ // Handle functions
1251
+ if (typeof value === 'function') {
1252
+ return '[Function]';
1253
+ }
1254
+
1255
+ return value;
1256
+ }));
1257
+ }
1258
+
1026
1259
  // ==================== PUBLIC API METHODS ====================
1027
1260
 
1028
1261
  /**
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-mpm
3
- Version: 4.1.22
3
+ Version: 4.1.24
4
4
  Summary: Claude Multi-Agent Project Manager - Orchestrate Claude with agent delegation and ticket tracking
5
5
  Author-email: Bob Matsuoka <bob@matsuoka.com>
6
6
  Maintainer: Claude MPM Team