mcp-ticketer 0.3.1__py3-none-any.whl → 0.3.2__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.
Potentially problematic release.
This version of mcp-ticketer might be problematic. Click here for more details.
- mcp_ticketer/__version__.py +1 -1
- mcp_ticketer/adapters/aitrackdown.py +12 -15
- mcp_ticketer/adapters/github.py +7 -4
- mcp_ticketer/adapters/jira.py +23 -22
- mcp_ticketer/adapters/linear/__init__.py +1 -1
- mcp_ticketer/adapters/linear/adapter.py +88 -89
- mcp_ticketer/adapters/linear/client.py +71 -52
- mcp_ticketer/adapters/linear/mappers.py +88 -68
- mcp_ticketer/adapters/linear/queries.py +28 -7
- mcp_ticketer/adapters/linear/types.py +57 -50
- mcp_ticketer/adapters/linear.py +2 -2
- mcp_ticketer/cli/adapter_diagnostics.py +86 -51
- mcp_ticketer/cli/diagnostics.py +165 -72
- mcp_ticketer/cli/linear_commands.py +156 -113
- mcp_ticketer/cli/main.py +153 -82
- mcp_ticketer/cli/simple_health.py +73 -45
- mcp_ticketer/cli/utils.py +15 -10
- mcp_ticketer/core/config.py +23 -19
- mcp_ticketer/core/env_discovery.py +5 -4
- mcp_ticketer/core/env_loader.py +109 -86
- mcp_ticketer/core/exceptions.py +20 -18
- mcp_ticketer/core/models.py +9 -0
- mcp_ticketer/core/project_config.py +1 -1
- mcp_ticketer/mcp/server.py +294 -139
- mcp_ticketer/queue/health_monitor.py +152 -121
- mcp_ticketer/queue/manager.py +11 -4
- mcp_ticketer/queue/queue.py +15 -3
- mcp_ticketer/queue/run_worker.py +1 -1
- mcp_ticketer/queue/ticket_registry.py +190 -132
- mcp_ticketer/queue/worker.py +54 -25
- {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.2.dist-info}/METADATA +1 -1
- mcp_ticketer-0.3.2.dist-info/RECORD +59 -0
- mcp_ticketer-0.3.1.dist-info/RECORD +0 -59
- {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.2.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.2.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.2.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.3.1.dist-info → mcp_ticketer-0.3.2.dist-info}/top_level.txt +0 -0
|
@@ -4,10 +4,10 @@ import os
|
|
|
4
4
|
from typing import Optional
|
|
5
5
|
|
|
6
6
|
import typer
|
|
7
|
+
from gql import Client, gql
|
|
8
|
+
from gql.transport.httpx import HTTPXTransport
|
|
7
9
|
from rich.console import Console
|
|
8
10
|
from rich.table import Table
|
|
9
|
-
from gql import gql, Client
|
|
10
|
-
from gql.transport.httpx import HTTPXTransport
|
|
11
11
|
|
|
12
12
|
app = typer.Typer(name="linear", help="Linear workspace and team management")
|
|
13
13
|
console = Console()
|
|
@@ -20,10 +20,9 @@ def _create_linear_client() -> Client:
|
|
|
20
20
|
console.print("[red]❌ LINEAR_API_KEY not found in environment[/red]")
|
|
21
21
|
console.print("Set it in .env.local or environment variables")
|
|
22
22
|
raise typer.Exit(1)
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
transport = HTTPXTransport(
|
|
25
|
-
url="https://api.linear.app/graphql",
|
|
26
|
-
headers={"Authorization": api_key}
|
|
25
|
+
url="https://api.linear.app/graphql", headers={"Authorization": api_key}
|
|
27
26
|
)
|
|
28
27
|
return Client(transport=transport, fetch_schema_from_transport=False)
|
|
29
28
|
|
|
@@ -32,9 +31,10 @@ def _create_linear_client() -> Client:
|
|
|
32
31
|
def list_workspaces():
|
|
33
32
|
"""List all accessible Linear workspaces."""
|
|
34
33
|
console.print("🔍 Discovering Linear workspaces...")
|
|
35
|
-
|
|
34
|
+
|
|
36
35
|
# Query for current organization and user info
|
|
37
|
-
query = gql(
|
|
36
|
+
query = gql(
|
|
37
|
+
"""
|
|
38
38
|
query GetWorkspaceInfo {
|
|
39
39
|
viewer {
|
|
40
40
|
id
|
|
@@ -48,26 +48,31 @@ def list_workspaces():
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
-
"""
|
|
52
|
-
|
|
51
|
+
"""
|
|
52
|
+
)
|
|
53
|
+
|
|
53
54
|
try:
|
|
54
55
|
client = _create_linear_client()
|
|
55
56
|
result = client.execute(query)
|
|
56
|
-
|
|
57
|
-
viewer = result.get(
|
|
58
|
-
organization = viewer.get(
|
|
59
|
-
|
|
57
|
+
|
|
58
|
+
viewer = result.get("viewer", {})
|
|
59
|
+
organization = viewer.get("organization", {})
|
|
60
|
+
|
|
60
61
|
console.print(f"\n👤 User: {viewer.get('name')} ({viewer.get('email')})")
|
|
61
|
-
console.print(
|
|
62
|
+
console.print("🏢 Current Workspace:")
|
|
62
63
|
console.print(f" Name: {organization.get('name')}")
|
|
63
64
|
console.print(f" URL Key: {organization.get('urlKey')}")
|
|
64
65
|
console.print(f" ID: {organization.get('id')}")
|
|
65
|
-
if organization.get(
|
|
66
|
+
if organization.get("createdAt"):
|
|
66
67
|
console.print(f" Created: {organization.get('createdAt')}")
|
|
67
|
-
|
|
68
|
-
console.print(
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
|
|
69
|
+
console.print(
|
|
70
|
+
f"\n✅ API key has access to: {organization.get('name')} workspace"
|
|
71
|
+
)
|
|
72
|
+
console.print(
|
|
73
|
+
f"🌐 Workspace URL: https://linear.app/{organization.get('urlKey')}"
|
|
74
|
+
)
|
|
75
|
+
|
|
71
76
|
except Exception as e:
|
|
72
77
|
console.print(f"[red]❌ Error fetching workspace info: {e}[/red]")
|
|
73
78
|
raise typer.Exit(1)
|
|
@@ -75,17 +80,22 @@ def list_workspaces():
|
|
|
75
80
|
|
|
76
81
|
@app.command("teams")
|
|
77
82
|
def list_teams(
|
|
78
|
-
workspace: Optional[str] = typer.Option(
|
|
79
|
-
|
|
83
|
+
workspace: Optional[str] = typer.Option(
|
|
84
|
+
None, "--workspace", "-w", help="Workspace URL key (optional)"
|
|
85
|
+
),
|
|
86
|
+
all_teams: bool = typer.Option(
|
|
87
|
+
False, "--all", "-a", help="Show all teams across all workspaces"
|
|
88
|
+
),
|
|
80
89
|
):
|
|
81
90
|
"""List all teams in the current workspace or all accessible teams."""
|
|
82
91
|
if all_teams:
|
|
83
92
|
console.print("🔍 Discovering ALL accessible Linear teams across workspaces...")
|
|
84
93
|
else:
|
|
85
94
|
console.print("🔍 Discovering Linear teams...")
|
|
86
|
-
|
|
95
|
+
|
|
87
96
|
# Query for all teams with pagination
|
|
88
|
-
query = gql(
|
|
97
|
+
query = gql(
|
|
98
|
+
"""
|
|
89
99
|
query GetTeams($first: Int, $after: String) {
|
|
90
100
|
viewer {
|
|
91
101
|
organization {
|
|
@@ -128,58 +138,70 @@ def list_teams(
|
|
|
128
138
|
}
|
|
129
139
|
}
|
|
130
140
|
}
|
|
131
|
-
"""
|
|
132
|
-
|
|
141
|
+
"""
|
|
142
|
+
)
|
|
143
|
+
|
|
133
144
|
try:
|
|
134
145
|
client = _create_linear_client()
|
|
135
|
-
|
|
146
|
+
|
|
136
147
|
# Fetch all teams with pagination
|
|
137
148
|
all_teams = []
|
|
138
149
|
has_next_page = True
|
|
139
150
|
after_cursor = None
|
|
140
151
|
current_workspace = None
|
|
141
|
-
|
|
152
|
+
|
|
142
153
|
while has_next_page:
|
|
143
154
|
variables = {"first": 50}
|
|
144
155
|
if after_cursor:
|
|
145
156
|
variables["after"] = after_cursor
|
|
146
|
-
|
|
157
|
+
|
|
147
158
|
result = client.execute(query, variable_values=variables)
|
|
148
|
-
|
|
159
|
+
|
|
149
160
|
# Get workspace info from first page
|
|
150
161
|
if current_workspace is None:
|
|
151
|
-
viewer = result.get(
|
|
152
|
-
current_workspace = viewer.get(
|
|
153
|
-
|
|
154
|
-
teams_data = result.get(
|
|
155
|
-
page_teams = teams_data.get(
|
|
156
|
-
page_info = teams_data.get(
|
|
157
|
-
|
|
162
|
+
viewer = result.get("viewer", {})
|
|
163
|
+
current_workspace = viewer.get("organization", {})
|
|
164
|
+
|
|
165
|
+
teams_data = result.get("teams", {})
|
|
166
|
+
page_teams = teams_data.get("nodes", [])
|
|
167
|
+
page_info = teams_data.get("pageInfo", {})
|
|
168
|
+
|
|
158
169
|
all_teams.extend(page_teams)
|
|
159
|
-
has_next_page = page_info.get(
|
|
160
|
-
after_cursor = page_info.get(
|
|
161
|
-
|
|
170
|
+
has_next_page = page_info.get("hasNextPage", False)
|
|
171
|
+
after_cursor = page_info.get("endCursor")
|
|
172
|
+
|
|
162
173
|
# Display workspace info
|
|
163
|
-
console.print(
|
|
164
|
-
|
|
174
|
+
console.print(
|
|
175
|
+
f"\n🏢 Workspace: {current_workspace.get('name')} ({current_workspace.get('urlKey')})"
|
|
176
|
+
)
|
|
177
|
+
|
|
165
178
|
# Filter teams by workspace if specified
|
|
166
179
|
if workspace:
|
|
167
|
-
filtered_teams = [
|
|
168
|
-
|
|
180
|
+
filtered_teams = [
|
|
181
|
+
team
|
|
182
|
+
for team in all_teams
|
|
183
|
+
if team.get("organization", {}).get("urlKey") == workspace
|
|
184
|
+
]
|
|
169
185
|
if not filtered_teams:
|
|
170
|
-
console.print(
|
|
186
|
+
console.print(
|
|
187
|
+
f"[yellow]No teams found in workspace '{workspace}'[/yellow]"
|
|
188
|
+
)
|
|
171
189
|
return
|
|
172
190
|
all_teams = filtered_teams
|
|
173
191
|
elif not all_teams and current_workspace:
|
|
174
192
|
# If not showing all teams, filter to current workspace only
|
|
175
|
-
filtered_teams = [
|
|
176
|
-
|
|
193
|
+
filtered_teams = [
|
|
194
|
+
team
|
|
195
|
+
for team in all_teams
|
|
196
|
+
if team.get("organization", {}).get("urlKey")
|
|
197
|
+
== current_workspace.get("urlKey")
|
|
198
|
+
]
|
|
177
199
|
all_teams = filtered_teams
|
|
178
|
-
|
|
200
|
+
|
|
179
201
|
if not all_teams:
|
|
180
202
|
console.print("[yellow]No teams found[/yellow]")
|
|
181
203
|
return
|
|
182
|
-
|
|
204
|
+
|
|
183
205
|
# Create table
|
|
184
206
|
title_suffix = " (all workspaces)" if all_teams else ""
|
|
185
207
|
table = Table(title=f"Linear Teams ({len(all_teams)} found){title_suffix}")
|
|
@@ -191,36 +213,36 @@ def list_teams(
|
|
|
191
213
|
table.add_column("Issues", justify="center")
|
|
192
214
|
table.add_column("Projects", justify="center")
|
|
193
215
|
table.add_column("Private", justify="center")
|
|
194
|
-
|
|
216
|
+
|
|
195
217
|
for team in all_teams:
|
|
196
|
-
member_count = len(team.get(
|
|
197
|
-
issue_count = len(team.get(
|
|
198
|
-
project_count = len(team.get(
|
|
199
|
-
is_private = "🔒" if team.get(
|
|
200
|
-
workspace_key = team.get(
|
|
218
|
+
member_count = len(team.get("members", {}).get("nodes", []))
|
|
219
|
+
issue_count = len(team.get("issues", {}).get("nodes", []))
|
|
220
|
+
project_count = len(team.get("projects", {}).get("nodes", []))
|
|
221
|
+
is_private = "🔒" if team.get("private") else "🌐"
|
|
222
|
+
workspace_key = team.get("organization", {}).get("urlKey", "")
|
|
201
223
|
|
|
202
224
|
table.add_row(
|
|
203
|
-
team.get(
|
|
204
|
-
team.get(
|
|
225
|
+
team.get("key", ""),
|
|
226
|
+
team.get("name", ""),
|
|
205
227
|
workspace_key,
|
|
206
|
-
team.get(
|
|
228
|
+
team.get("id", ""),
|
|
207
229
|
str(member_count),
|
|
208
230
|
str(issue_count),
|
|
209
231
|
str(project_count),
|
|
210
|
-
is_private
|
|
232
|
+
is_private,
|
|
211
233
|
)
|
|
212
|
-
|
|
234
|
+
|
|
213
235
|
console.print(table)
|
|
214
|
-
|
|
236
|
+
|
|
215
237
|
# Show configuration suggestions
|
|
216
238
|
if all_teams:
|
|
217
|
-
console.print(
|
|
239
|
+
console.print("\n💡 Configuration suggestions:")
|
|
218
240
|
for team in all_teams[:3]: # Show first 3 teams
|
|
219
241
|
console.print(f" Team '{team.get('name')}':")
|
|
220
242
|
console.print(f" team_key: '{team.get('key')}'")
|
|
221
243
|
console.print(f" team_id: '{team.get('id')}'")
|
|
222
244
|
console.print()
|
|
223
|
-
|
|
245
|
+
|
|
224
246
|
except Exception as e:
|
|
225
247
|
console.print(f"[red]❌ Error fetching teams: {e}[/red]")
|
|
226
248
|
raise typer.Exit(1)
|
|
@@ -228,9 +250,13 @@ def list_teams(
|
|
|
228
250
|
|
|
229
251
|
@app.command("configure")
|
|
230
252
|
def configure_team(
|
|
231
|
-
team_key: Optional[str] = typer.Option(
|
|
253
|
+
team_key: Optional[str] = typer.Option(
|
|
254
|
+
None, "--team-key", "-k", help="Team key (e.g., '1M')"
|
|
255
|
+
),
|
|
232
256
|
team_id: Optional[str] = typer.Option(None, "--team-id", "-i", help="Team UUID"),
|
|
233
|
-
workspace: Optional[str] = typer.Option(
|
|
257
|
+
workspace: Optional[str] = typer.Option(
|
|
258
|
+
None, "--workspace", "-w", help="Workspace URL key"
|
|
259
|
+
),
|
|
234
260
|
):
|
|
235
261
|
"""Configure Linear adapter with a specific team."""
|
|
236
262
|
from ..cli.main import load_config, save_config
|
|
@@ -244,7 +270,8 @@ def configure_team(
|
|
|
244
270
|
# Validate team exists
|
|
245
271
|
if team_id:
|
|
246
272
|
# Validate team by ID
|
|
247
|
-
query = gql(
|
|
273
|
+
query = gql(
|
|
274
|
+
"""
|
|
248
275
|
query GetTeamById($id: String!) {
|
|
249
276
|
team(id: $id) {
|
|
250
277
|
id
|
|
@@ -256,12 +283,13 @@ def configure_team(
|
|
|
256
283
|
}
|
|
257
284
|
}
|
|
258
285
|
}
|
|
259
|
-
"""
|
|
286
|
+
"""
|
|
287
|
+
)
|
|
260
288
|
|
|
261
289
|
try:
|
|
262
290
|
client = _create_linear_client()
|
|
263
291
|
result = client.execute(query, variable_values={"id": team_id})
|
|
264
|
-
team = result.get(
|
|
292
|
+
team = result.get("team")
|
|
265
293
|
|
|
266
294
|
if not team:
|
|
267
295
|
console.print(f"[red]❌ Team with ID '{team_id}' not found[/red]")
|
|
@@ -273,7 +301,8 @@ def configure_team(
|
|
|
273
301
|
|
|
274
302
|
elif team_key:
|
|
275
303
|
# Validate team by key
|
|
276
|
-
query = gql(
|
|
304
|
+
query = gql(
|
|
305
|
+
"""
|
|
277
306
|
query GetTeamByKey($key: String!) {
|
|
278
307
|
teams(filter: { key: { eq: $key } }) {
|
|
279
308
|
nodes {
|
|
@@ -287,19 +316,20 @@ def configure_team(
|
|
|
287
316
|
}
|
|
288
317
|
}
|
|
289
318
|
}
|
|
290
|
-
"""
|
|
319
|
+
"""
|
|
320
|
+
)
|
|
291
321
|
|
|
292
322
|
try:
|
|
293
323
|
client = _create_linear_client()
|
|
294
324
|
result = client.execute(query, variable_values={"key": team_key})
|
|
295
|
-
teams = result.get(
|
|
325
|
+
teams = result.get("teams", {}).get("nodes", [])
|
|
296
326
|
|
|
297
327
|
if not teams:
|
|
298
328
|
console.print(f"[red]❌ Team with key '{team_key}' not found[/red]")
|
|
299
329
|
raise typer.Exit(1)
|
|
300
330
|
|
|
301
331
|
team = teams[0]
|
|
302
|
-
team_id = team[
|
|
332
|
+
team_id = team["id"] # Use the found team ID
|
|
303
333
|
|
|
304
334
|
except Exception as e:
|
|
305
335
|
console.print(f"[red]❌ Error validating team: {e}[/red]")
|
|
@@ -313,10 +343,7 @@ def configure_team(
|
|
|
313
343
|
config["adapters"] = {}
|
|
314
344
|
|
|
315
345
|
# Update Linear adapter configuration
|
|
316
|
-
linear_config = {
|
|
317
|
-
"type": "linear",
|
|
318
|
-
"team_id": team_id
|
|
319
|
-
}
|
|
346
|
+
linear_config = {"type": "linear", "team_id": team_id}
|
|
320
347
|
|
|
321
348
|
if team_key:
|
|
322
349
|
linear_config["team_key"] = team_key
|
|
@@ -327,23 +354,27 @@ def configure_team(
|
|
|
327
354
|
|
|
328
355
|
# Save configuration
|
|
329
356
|
save_config(config)
|
|
330
|
-
|
|
331
|
-
console.print(
|
|
357
|
+
|
|
358
|
+
console.print("✅ Linear adapter configured successfully!")
|
|
332
359
|
console.print(f" Team: {team.get('name')} ({team.get('key')})")
|
|
333
360
|
console.print(f" Team ID: {team_id}")
|
|
334
361
|
console.print(f" Workspace: {team.get('organization', {}).get('name')}")
|
|
335
|
-
|
|
362
|
+
|
|
336
363
|
# Test the configuration
|
|
337
364
|
console.print("\n🧪 Testing configuration...")
|
|
338
365
|
try:
|
|
339
366
|
from ..adapters.linear import LinearAdapter
|
|
367
|
+
|
|
340
368
|
adapter = LinearAdapter(linear_config)
|
|
341
|
-
|
|
369
|
+
|
|
342
370
|
# Test by listing a few tickets
|
|
343
371
|
import asyncio
|
|
372
|
+
|
|
344
373
|
tickets = asyncio.run(adapter.list(limit=1))
|
|
345
|
-
console.print(
|
|
346
|
-
|
|
374
|
+
console.print(
|
|
375
|
+
f"✅ Configuration test successful! Found {len(tickets)} ticket(s)"
|
|
376
|
+
)
|
|
377
|
+
|
|
347
378
|
except Exception as e:
|
|
348
379
|
console.print(f"[yellow]⚠️ Configuration saved but test failed: {e}[/yellow]")
|
|
349
380
|
console.print("You may need to check your API key or team permissions")
|
|
@@ -351,17 +382,22 @@ def configure_team(
|
|
|
351
382
|
|
|
352
383
|
@app.command("info")
|
|
353
384
|
def show_info(
|
|
354
|
-
team_key: Optional[str] = typer.Option(
|
|
355
|
-
|
|
385
|
+
team_key: Optional[str] = typer.Option(
|
|
386
|
+
None, "--team-key", "-k", help="Team key to show info for"
|
|
387
|
+
),
|
|
388
|
+
team_id: Optional[str] = typer.Option(
|
|
389
|
+
None, "--team-id", "-i", help="Team UUID to show info for"
|
|
390
|
+
),
|
|
356
391
|
):
|
|
357
392
|
"""Show detailed information about a specific team."""
|
|
358
393
|
if not team_key and not team_id:
|
|
359
394
|
console.print("[red]❌ Either --team-key or --team-id is required[/red]")
|
|
360
395
|
raise typer.Exit(1)
|
|
361
|
-
|
|
396
|
+
|
|
362
397
|
# Query for detailed team information
|
|
363
398
|
if team_id:
|
|
364
|
-
query = gql(
|
|
399
|
+
query = gql(
|
|
400
|
+
"""
|
|
365
401
|
query GetTeamInfo($id: String!) {
|
|
366
402
|
team(id: $id) {
|
|
367
403
|
id
|
|
@@ -392,10 +428,12 @@ def show_info(
|
|
|
392
428
|
}
|
|
393
429
|
}
|
|
394
430
|
}
|
|
395
|
-
"""
|
|
431
|
+
"""
|
|
432
|
+
)
|
|
396
433
|
variables = {"id": team_id}
|
|
397
434
|
else:
|
|
398
|
-
query = gql(
|
|
435
|
+
query = gql(
|
|
436
|
+
"""
|
|
399
437
|
query GetTeamInfoByKey($key: String!) {
|
|
400
438
|
teams(filter: { key: { eq: $key } }) {
|
|
401
439
|
nodes {
|
|
@@ -428,63 +466,68 @@ def show_info(
|
|
|
428
466
|
}
|
|
429
467
|
}
|
|
430
468
|
}
|
|
431
|
-
"""
|
|
469
|
+
"""
|
|
470
|
+
)
|
|
432
471
|
variables = {"key": team_key}
|
|
433
|
-
|
|
472
|
+
|
|
434
473
|
try:
|
|
435
474
|
client = _create_linear_client()
|
|
436
475
|
result = client.execute(query, variable_values=variables)
|
|
437
|
-
|
|
476
|
+
|
|
438
477
|
if team_id:
|
|
439
|
-
team = result.get(
|
|
478
|
+
team = result.get("team")
|
|
440
479
|
else:
|
|
441
|
-
teams = result.get(
|
|
480
|
+
teams = result.get("teams", {}).get("nodes", [])
|
|
442
481
|
team = teams[0] if teams else None
|
|
443
|
-
|
|
482
|
+
|
|
444
483
|
if not team:
|
|
445
484
|
identifier = team_id or team_key
|
|
446
485
|
console.print(f"[red]❌ Team '{identifier}' not found[/red]")
|
|
447
486
|
raise typer.Exit(1)
|
|
448
|
-
|
|
487
|
+
|
|
449
488
|
# Display team information
|
|
450
489
|
console.print(f"\n🏷️ Team: {team.get('name')}")
|
|
451
490
|
console.print(f" Key: {team.get('key')}")
|
|
452
491
|
console.print(f" ID: {team.get('id')}")
|
|
453
|
-
console.print(
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
492
|
+
console.print(
|
|
493
|
+
f" Workspace: {team.get('organization', {}).get('name')} ({team.get('organization', {}).get('urlKey')})"
|
|
494
|
+
)
|
|
495
|
+
console.print(
|
|
496
|
+
f" Privacy: {'🔒 Private' if team.get('private') else '🌐 Public'}"
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
if team.get("description"):
|
|
457
500
|
console.print(f" Description: {team.get('description')}")
|
|
458
|
-
|
|
501
|
+
|
|
459
502
|
console.print(f" Created: {team.get('createdAt')}")
|
|
460
|
-
|
|
503
|
+
|
|
461
504
|
# Statistics
|
|
462
|
-
member_count = len(team.get(
|
|
463
|
-
state_count = len(team.get(
|
|
464
|
-
|
|
465
|
-
console.print(
|
|
505
|
+
member_count = len(team.get("members", {}).get("nodes", []))
|
|
506
|
+
state_count = len(team.get("states", {}).get("nodes", []))
|
|
507
|
+
|
|
508
|
+
console.print("\n📊 Statistics:")
|
|
466
509
|
console.print(f" Members: {member_count}")
|
|
467
510
|
console.print(f" Workflow States: {state_count}")
|
|
468
|
-
|
|
511
|
+
|
|
469
512
|
# Show members
|
|
470
|
-
members = team.get(
|
|
513
|
+
members = team.get("members", {}).get("nodes", [])
|
|
471
514
|
if members:
|
|
472
515
|
console.print(f"\n👥 Members ({len(members)}):")
|
|
473
516
|
for member in members:
|
|
474
|
-
status = "✅" if member.get(
|
|
517
|
+
status = "✅" if member.get("active") else "❌"
|
|
475
518
|
console.print(f" {status} {member.get('name')}")
|
|
476
519
|
if len(members) == 10:
|
|
477
|
-
console.print(
|
|
478
|
-
|
|
520
|
+
console.print(" ... (showing first 10 members)")
|
|
521
|
+
|
|
479
522
|
# Show workflow states
|
|
480
|
-
states = team.get(
|
|
523
|
+
states = team.get("states", {}).get("nodes", [])
|
|
481
524
|
if states:
|
|
482
525
|
console.print(f"\n🔄 Workflow States ({len(states)}):")
|
|
483
|
-
for state in sorted(states, key=lambda s: s.get(
|
|
526
|
+
for state in sorted(states, key=lambda s: s.get("position", 0)):
|
|
484
527
|
console.print(f" {state.get('name')} ({state.get('type')})")
|
|
485
528
|
if len(states) == 20:
|
|
486
|
-
console.print(
|
|
487
|
-
|
|
529
|
+
console.print(" ... (showing first 20 states)")
|
|
530
|
+
|
|
488
531
|
except Exception as e:
|
|
489
532
|
console.print(f"[red]❌ Error fetching team info: {e}[/red]")
|
|
490
533
|
raise typer.Exit(1)
|