mcp-ticketer 0.4.11__py3-none-any.whl → 2.0.1__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/__init__.py +10 -10
- mcp_ticketer/__version__.py +3 -3
- mcp_ticketer/adapters/__init__.py +2 -0
- mcp_ticketer/adapters/aitrackdown.py +394 -9
- mcp_ticketer/adapters/asana/__init__.py +15 -0
- mcp_ticketer/adapters/asana/adapter.py +1416 -0
- mcp_ticketer/adapters/asana/client.py +292 -0
- mcp_ticketer/adapters/asana/mappers.py +348 -0
- mcp_ticketer/adapters/asana/types.py +146 -0
- mcp_ticketer/adapters/github.py +836 -105
- mcp_ticketer/adapters/hybrid.py +47 -5
- mcp_ticketer/adapters/jira.py +772 -1
- mcp_ticketer/adapters/linear/adapter.py +2293 -108
- mcp_ticketer/adapters/linear/client.py +146 -12
- mcp_ticketer/adapters/linear/mappers.py +105 -11
- mcp_ticketer/adapters/linear/queries.py +168 -1
- mcp_ticketer/adapters/linear/types.py +80 -4
- mcp_ticketer/analysis/__init__.py +56 -0
- mcp_ticketer/analysis/dependency_graph.py +255 -0
- mcp_ticketer/analysis/health_assessment.py +304 -0
- mcp_ticketer/analysis/orphaned.py +218 -0
- mcp_ticketer/analysis/project_status.py +594 -0
- mcp_ticketer/analysis/similarity.py +224 -0
- mcp_ticketer/analysis/staleness.py +266 -0
- mcp_ticketer/automation/__init__.py +11 -0
- mcp_ticketer/automation/project_updates.py +378 -0
- mcp_ticketer/cache/memory.py +3 -3
- mcp_ticketer/cli/adapter_diagnostics.py +4 -2
- mcp_ticketer/cli/auggie_configure.py +18 -6
- mcp_ticketer/cli/codex_configure.py +175 -60
- mcp_ticketer/cli/configure.py +884 -146
- mcp_ticketer/cli/cursor_configure.py +314 -0
- mcp_ticketer/cli/diagnostics.py +31 -28
- mcp_ticketer/cli/discover.py +293 -21
- mcp_ticketer/cli/gemini_configure.py +18 -6
- mcp_ticketer/cli/init_command.py +880 -0
- mcp_ticketer/cli/instruction_commands.py +435 -0
- mcp_ticketer/cli/linear_commands.py +99 -15
- mcp_ticketer/cli/main.py +109 -2055
- mcp_ticketer/cli/mcp_configure.py +673 -99
- mcp_ticketer/cli/mcp_server_commands.py +415 -0
- mcp_ticketer/cli/migrate_config.py +12 -8
- mcp_ticketer/cli/platform_commands.py +6 -6
- mcp_ticketer/cli/platform_detection.py +477 -0
- mcp_ticketer/cli/platform_installer.py +536 -0
- mcp_ticketer/cli/project_update_commands.py +350 -0
- mcp_ticketer/cli/queue_commands.py +15 -15
- mcp_ticketer/cli/setup_command.py +639 -0
- mcp_ticketer/cli/simple_health.py +13 -11
- mcp_ticketer/cli/ticket_commands.py +277 -36
- mcp_ticketer/cli/update_checker.py +313 -0
- mcp_ticketer/cli/utils.py +45 -41
- mcp_ticketer/core/__init__.py +35 -1
- mcp_ticketer/core/adapter.py +170 -5
- mcp_ticketer/core/config.py +38 -31
- mcp_ticketer/core/env_discovery.py +33 -3
- mcp_ticketer/core/env_loader.py +7 -6
- mcp_ticketer/core/exceptions.py +10 -4
- mcp_ticketer/core/http_client.py +10 -10
- mcp_ticketer/core/instructions.py +405 -0
- mcp_ticketer/core/label_manager.py +732 -0
- mcp_ticketer/core/mappers.py +32 -20
- mcp_ticketer/core/models.py +136 -1
- mcp_ticketer/core/onepassword_secrets.py +379 -0
- mcp_ticketer/core/priority_matcher.py +463 -0
- mcp_ticketer/core/project_config.py +148 -14
- mcp_ticketer/core/registry.py +1 -1
- mcp_ticketer/core/session_state.py +171 -0
- mcp_ticketer/core/state_matcher.py +592 -0
- mcp_ticketer/core/url_parser.py +425 -0
- mcp_ticketer/core/validators.py +69 -0
- mcp_ticketer/defaults/ticket_instructions.md +644 -0
- mcp_ticketer/mcp/__init__.py +2 -2
- mcp_ticketer/mcp/server/__init__.py +2 -2
- mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
- mcp_ticketer/mcp/server/main.py +187 -93
- mcp_ticketer/mcp/server/routing.py +655 -0
- mcp_ticketer/mcp/server/server_sdk.py +58 -0
- mcp_ticketer/mcp/server/tools/__init__.py +37 -9
- mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
- mcp_ticketer/mcp/server/tools/attachment_tools.py +65 -20
- mcp_ticketer/mcp/server/tools/bulk_tools.py +259 -202
- mcp_ticketer/mcp/server/tools/comment_tools.py +74 -12
- mcp_ticketer/mcp/server/tools/config_tools.py +1429 -0
- mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
- mcp_ticketer/mcp/server/tools/hierarchy_tools.py +878 -319
- mcp_ticketer/mcp/server/tools/instruction_tools.py +295 -0
- mcp_ticketer/mcp/server/tools/label_tools.py +942 -0
- mcp_ticketer/mcp/server/tools/pr_tools.py +3 -7
- mcp_ticketer/mcp/server/tools/project_status_tools.py +158 -0
- mcp_ticketer/mcp/server/tools/project_update_tools.py +473 -0
- mcp_ticketer/mcp/server/tools/search_tools.py +180 -97
- mcp_ticketer/mcp/server/tools/session_tools.py +308 -0
- mcp_ticketer/mcp/server/tools/ticket_tools.py +1182 -82
- mcp_ticketer/mcp/server/tools/user_ticket_tools.py +364 -0
- mcp_ticketer/queue/health_monitor.py +1 -0
- mcp_ticketer/queue/manager.py +4 -4
- mcp_ticketer/queue/queue.py +3 -3
- mcp_ticketer/queue/run_worker.py +1 -1
- mcp_ticketer/queue/ticket_registry.py +2 -2
- mcp_ticketer/queue/worker.py +15 -13
- mcp_ticketer/utils/__init__.py +5 -0
- mcp_ticketer/utils/token_utils.py +246 -0
- mcp_ticketer-2.0.1.dist-info/METADATA +1366 -0
- mcp_ticketer-2.0.1.dist-info/RECORD +122 -0
- mcp_ticketer-0.4.11.dist-info/METADATA +0 -496
- mcp_ticketer-0.4.11.dist-info/RECORD +0 -77
- {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-2.0.1.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-2.0.1.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-2.0.1.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-2.0.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
"""CLI commands for project updates (Linear project status updates)."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
|
|
11
|
+
from ..core.models import ProjectUpdateHealth
|
|
12
|
+
|
|
13
|
+
app = typer.Typer(name="project-update", help="Project status update management")
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _format_health_indicator(health: ProjectUpdateHealth | str | None) -> str:
|
|
18
|
+
"""Format health status with color-coded indicators.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
health: Health status enum or string value
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Formatted health indicator with color and icon
|
|
25
|
+
"""
|
|
26
|
+
if not health:
|
|
27
|
+
return "[dim]○ Not Set[/dim]"
|
|
28
|
+
|
|
29
|
+
# Handle both enum and string values
|
|
30
|
+
health_str = health.value if isinstance(health, ProjectUpdateHealth) else health
|
|
31
|
+
|
|
32
|
+
health_indicators = {
|
|
33
|
+
"on_track": "[green]✓ On Track[/green]",
|
|
34
|
+
"at_risk": "[yellow]⚠ At Risk[/yellow]",
|
|
35
|
+
"off_track": "[red]✗ Off Track[/red]",
|
|
36
|
+
"complete": "[blue]✓ Complete[/blue]",
|
|
37
|
+
"inactive": "[dim]○ Inactive[/dim]",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return health_indicators.get(health_str, f"[dim]{health_str}[/dim]")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _format_relative_time(dt: datetime) -> str:
|
|
44
|
+
"""Format datetime as relative time string.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
dt: Datetime to format
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Human-readable relative time (e.g., "2 hours ago")
|
|
51
|
+
"""
|
|
52
|
+
try:
|
|
53
|
+
from humanize import naturaltime
|
|
54
|
+
|
|
55
|
+
return naturaltime(dt)
|
|
56
|
+
except ImportError:
|
|
57
|
+
# Fallback to ISO format if humanize not available
|
|
58
|
+
return dt.strftime("%Y-%m-%d %H:%M:%S")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
async def _create_update_async(
|
|
62
|
+
project_id: str,
|
|
63
|
+
body: str,
|
|
64
|
+
health: ProjectUpdateHealth | None = None,
|
|
65
|
+
) -> None:
|
|
66
|
+
"""Async implementation of create command."""
|
|
67
|
+
from .main import get_adapter
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
adapter = get_adapter()
|
|
71
|
+
|
|
72
|
+
# Check if adapter supports project updates
|
|
73
|
+
if not hasattr(adapter, "create_project_update"):
|
|
74
|
+
adapter_name = getattr(adapter, "adapter_name", type(adapter).__name__)
|
|
75
|
+
console.print(
|
|
76
|
+
f"[red]✗[/red] Adapter '{adapter_name}' does not support project updates"
|
|
77
|
+
)
|
|
78
|
+
console.print(
|
|
79
|
+
"[dim]Project updates are supported by: Linear, GitHub V2, Asana[/dim]"
|
|
80
|
+
)
|
|
81
|
+
raise typer.Exit(1) from None
|
|
82
|
+
|
|
83
|
+
# Validate body is not empty
|
|
84
|
+
if not body or not body.strip():
|
|
85
|
+
console.print("[red]✗[/red] Update body cannot be empty")
|
|
86
|
+
raise typer.Exit(1) from None
|
|
87
|
+
|
|
88
|
+
# Create the project update
|
|
89
|
+
console.print(f"[dim]Creating project update for '{project_id}'...[/dim]")
|
|
90
|
+
update = await adapter.create_project_update(
|
|
91
|
+
project_id=project_id,
|
|
92
|
+
body=body,
|
|
93
|
+
health=health,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Display success message with update details
|
|
97
|
+
console.print("\n[green]✓[/green] Project update created successfully!")
|
|
98
|
+
console.print(f" Update ID: [cyan]{update.id}[/cyan]")
|
|
99
|
+
console.print(
|
|
100
|
+
f" Project: [bold]{update.project_name or update.project_id}[/bold]"
|
|
101
|
+
)
|
|
102
|
+
console.print(f" Health: {_format_health_indicator(update.health)}")
|
|
103
|
+
console.print(f" Created: {update.created_at.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
104
|
+
|
|
105
|
+
if update.url:
|
|
106
|
+
console.print(f" URL: [link]{update.url}[/link]")
|
|
107
|
+
|
|
108
|
+
# Show preview of body
|
|
109
|
+
preview = body[:100] + "..." if len(body) > 100 else body
|
|
110
|
+
console.print(f" Body Preview: {preview}")
|
|
111
|
+
|
|
112
|
+
except ValueError as e:
|
|
113
|
+
console.print(f"[red]✗[/red] {str(e)}")
|
|
114
|
+
raise typer.Exit(1) from e
|
|
115
|
+
except Exception as e:
|
|
116
|
+
console.print(f"[red]✗[/red] Unexpected error: {str(e)}")
|
|
117
|
+
raise typer.Exit(1) from e
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
async def _list_updates_async(
|
|
121
|
+
project_id: str,
|
|
122
|
+
limit: int = 10,
|
|
123
|
+
) -> None:
|
|
124
|
+
"""Async implementation of list command."""
|
|
125
|
+
from .main import get_adapter
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
adapter = get_adapter()
|
|
129
|
+
|
|
130
|
+
# Check if adapter supports project updates
|
|
131
|
+
if not hasattr(adapter, "list_project_updates"):
|
|
132
|
+
adapter_name = getattr(adapter, "adapter_name", type(adapter).__name__)
|
|
133
|
+
console.print(
|
|
134
|
+
f"[red]✗[/red] Adapter '{adapter_name}' does not support project updates"
|
|
135
|
+
)
|
|
136
|
+
raise typer.Exit(1) from None
|
|
137
|
+
|
|
138
|
+
# List project updates
|
|
139
|
+
console.print(f"[dim]Fetching updates for project '{project_id}'...[/dim]")
|
|
140
|
+
updates = await adapter.list_project_updates(
|
|
141
|
+
project_id=project_id,
|
|
142
|
+
limit=limit,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
if not updates:
|
|
146
|
+
console.print(
|
|
147
|
+
f"\n[yellow]No updates found for project '{project_id}'[/yellow]"
|
|
148
|
+
)
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
# Create Rich table
|
|
152
|
+
table = Table(title=f"Project Updates ({len(updates)} total)")
|
|
153
|
+
table.add_column("ID", style="cyan", no_wrap=True)
|
|
154
|
+
table.add_column("Date", style="dim")
|
|
155
|
+
table.add_column("Health", no_wrap=True)
|
|
156
|
+
table.add_column("Author", style="blue")
|
|
157
|
+
table.add_column("Preview", style="white")
|
|
158
|
+
|
|
159
|
+
for update in updates:
|
|
160
|
+
# Format date
|
|
161
|
+
date_str = (
|
|
162
|
+
_format_relative_time(update.created_at)
|
|
163
|
+
if update.created_at
|
|
164
|
+
else "Unknown"
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
# Format health
|
|
168
|
+
health_str = _format_health_indicator(update.health)
|
|
169
|
+
|
|
170
|
+
# Format author
|
|
171
|
+
author = update.author_name or update.author_id or "Unknown"
|
|
172
|
+
|
|
173
|
+
# Preview (first 50 chars of body)
|
|
174
|
+
preview = update.body[:50] + "..." if len(update.body) > 50 else update.body
|
|
175
|
+
|
|
176
|
+
table.add_row(
|
|
177
|
+
update.id[:8] + "..." if len(update.id) > 8 else update.id,
|
|
178
|
+
date_str,
|
|
179
|
+
health_str,
|
|
180
|
+
author,
|
|
181
|
+
preview,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
console.print(table)
|
|
185
|
+
|
|
186
|
+
except ValueError as e:
|
|
187
|
+
console.print(f"[red]✗[/red] {str(e)}")
|
|
188
|
+
raise typer.Exit(1) from e
|
|
189
|
+
except Exception as e:
|
|
190
|
+
console.print(f"[red]✗[/red] Unexpected error: {str(e)}")
|
|
191
|
+
raise typer.Exit(1) from e
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
async def _get_update_async(update_id: str) -> None:
|
|
195
|
+
"""Async implementation of get command."""
|
|
196
|
+
from .main import get_adapter
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
adapter = get_adapter()
|
|
200
|
+
|
|
201
|
+
# Check if adapter supports project updates
|
|
202
|
+
if not hasattr(adapter, "get_project_update"):
|
|
203
|
+
adapter_name = getattr(adapter, "adapter_name", type(adapter).__name__)
|
|
204
|
+
console.print(
|
|
205
|
+
f"[red]✗[/red] Adapter '{adapter_name}' does not support project updates"
|
|
206
|
+
)
|
|
207
|
+
raise typer.Exit(1) from None
|
|
208
|
+
|
|
209
|
+
# Get project update
|
|
210
|
+
console.print(f"[dim]Fetching update '{update_id}'...[/dim]")
|
|
211
|
+
update = await adapter.get_project_update(update_id=update_id)
|
|
212
|
+
|
|
213
|
+
# Build detailed display content
|
|
214
|
+
content_lines = [
|
|
215
|
+
f"[bold]Update ID:[/bold] {update.id}",
|
|
216
|
+
f"[bold]Project:[/bold] {update.project_name or update.project_id}",
|
|
217
|
+
f"[bold]Health:[/bold] {_format_health_indicator(update.health)}",
|
|
218
|
+
f"[bold]Created:[/bold] {update.created_at.strftime('%Y-%m-%d %H:%M:%S')}",
|
|
219
|
+
]
|
|
220
|
+
|
|
221
|
+
if update.author_name or update.author_id:
|
|
222
|
+
author = update.author_name or update.author_id
|
|
223
|
+
content_lines.append(f"[bold]Author:[/bold] {author}")
|
|
224
|
+
|
|
225
|
+
if update.url:
|
|
226
|
+
content_lines.append(f"[bold]URL:[/bold] [link]{update.url}[/link]")
|
|
227
|
+
|
|
228
|
+
# Add body
|
|
229
|
+
content_lines.append("")
|
|
230
|
+
content_lines.append("[bold]Body:[/bold]")
|
|
231
|
+
content_lines.append(update.body)
|
|
232
|
+
|
|
233
|
+
# Add Linear-specific fields if present
|
|
234
|
+
if update.diff_markdown:
|
|
235
|
+
content_lines.append("")
|
|
236
|
+
content_lines.append("[bold]Diff (Changes Since Last Update):[/bold]")
|
|
237
|
+
content_lines.append(update.diff_markdown)
|
|
238
|
+
|
|
239
|
+
if update.is_stale is not None:
|
|
240
|
+
stale_indicator = (
|
|
241
|
+
"[red]⚠ Stale[/red]" if update.is_stale else "[green]✓ Current[/green]"
|
|
242
|
+
)
|
|
243
|
+
content_lines.append("")
|
|
244
|
+
content_lines.append(f"[bold]Freshness:[/bold] {stale_indicator}")
|
|
245
|
+
|
|
246
|
+
# Display as Rich panel
|
|
247
|
+
panel = Panel(
|
|
248
|
+
"\n".join(content_lines),
|
|
249
|
+
title="[bold]Project Update Details[/bold]",
|
|
250
|
+
border_style="cyan",
|
|
251
|
+
expand=False,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
console.print(panel)
|
|
255
|
+
|
|
256
|
+
except ValueError as e:
|
|
257
|
+
console.print(f"[red]✗[/red] {str(e)}")
|
|
258
|
+
raise typer.Exit(1) from e
|
|
259
|
+
except Exception as e:
|
|
260
|
+
console.print(f"[red]✗[/red] Unexpected error: {str(e)}")
|
|
261
|
+
raise typer.Exit(1) from e
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@app.command("create")
|
|
265
|
+
def create_update(
|
|
266
|
+
project_id: str = typer.Argument(
|
|
267
|
+
...,
|
|
268
|
+
help="Project identifier (UUID, slugId, short ID, or URL)",
|
|
269
|
+
),
|
|
270
|
+
body: str = typer.Argument(
|
|
271
|
+
...,
|
|
272
|
+
help="Update content (markdown formatted)",
|
|
273
|
+
),
|
|
274
|
+
health: ProjectUpdateHealth | None = typer.Option(
|
|
275
|
+
None,
|
|
276
|
+
"--health",
|
|
277
|
+
"-h",
|
|
278
|
+
help="Project health status (on_track, at_risk, off_track, complete, inactive)",
|
|
279
|
+
),
|
|
280
|
+
) -> None:
|
|
281
|
+
"""Create a new project status update.
|
|
282
|
+
|
|
283
|
+
Creates a status update for a project with optional health indicator.
|
|
284
|
+
Linear projects will automatically generate a diff showing changes.
|
|
285
|
+
|
|
286
|
+
Examples:
|
|
287
|
+
# Create update with health status
|
|
288
|
+
mcp-ticket project-update create "PROJ-123" "Sprint completed successfully" --health on_track
|
|
289
|
+
|
|
290
|
+
# Create update using project URL
|
|
291
|
+
mcp-ticket project-update create "https://linear.app/team/project/proj-slug" "Weekly update"
|
|
292
|
+
|
|
293
|
+
# Create update without health indicator
|
|
294
|
+
mcp-ticket project-update create "mcp-ticketer-eac28953c267" "Development ongoing"
|
|
295
|
+
"""
|
|
296
|
+
asyncio.run(_create_update_async(project_id, body, health))
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
@app.command("list")
|
|
300
|
+
def list_updates(
|
|
301
|
+
project_id: str = typer.Argument(
|
|
302
|
+
...,
|
|
303
|
+
help="Project identifier (UUID, slugId, short ID, or URL)",
|
|
304
|
+
),
|
|
305
|
+
limit: int = typer.Option(
|
|
306
|
+
10,
|
|
307
|
+
"--limit",
|
|
308
|
+
"-l",
|
|
309
|
+
help="Maximum number of updates to return",
|
|
310
|
+
min=1,
|
|
311
|
+
max=100,
|
|
312
|
+
),
|
|
313
|
+
) -> None:
|
|
314
|
+
"""List recent project updates.
|
|
315
|
+
|
|
316
|
+
Retrieves recent status updates for a project, ordered by creation date (newest first).
|
|
317
|
+
|
|
318
|
+
Examples:
|
|
319
|
+
# List last 10 updates (default)
|
|
320
|
+
mcp-ticket project-update list "PROJ-123"
|
|
321
|
+
|
|
322
|
+
# List last 20 updates
|
|
323
|
+
mcp-ticket project-update list "PROJ-123" --limit 20
|
|
324
|
+
|
|
325
|
+
# List updates using project URL
|
|
326
|
+
mcp-ticket project-update list "https://linear.app/team/project/proj-slug"
|
|
327
|
+
"""
|
|
328
|
+
asyncio.run(_list_updates_async(project_id, limit))
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
@app.command("get")
|
|
332
|
+
def get_update(
|
|
333
|
+
update_id: str = typer.Argument(
|
|
334
|
+
...,
|
|
335
|
+
help="Project update identifier (UUID)",
|
|
336
|
+
),
|
|
337
|
+
) -> None:
|
|
338
|
+
"""Get detailed information about a specific project update.
|
|
339
|
+
|
|
340
|
+
Retrieves full details including body, health status, author, and Linear-specific
|
|
341
|
+
fields like auto-generated diffs and staleness indicators.
|
|
342
|
+
|
|
343
|
+
Examples:
|
|
344
|
+
# Get update details
|
|
345
|
+
mcp-ticket project-update get "update-uuid-here"
|
|
346
|
+
|
|
347
|
+
# View update with full diff
|
|
348
|
+
mcp-ticket project-update get "abc123def456"
|
|
349
|
+
"""
|
|
350
|
+
asyncio.run(_get_update_async(update_id))
|
|
@@ -16,7 +16,7 @@ console = Console()
|
|
|
16
16
|
def list_queue(
|
|
17
17
|
status: QueueStatus = typer.Option(None, "--status", "-s", help="Filter by status"),
|
|
18
18
|
limit: int = typer.Option(25, "--limit", "-l", help="Maximum items to show"),
|
|
19
|
-
):
|
|
19
|
+
) -> None:
|
|
20
20
|
"""List queue items."""
|
|
21
21
|
queue = Queue()
|
|
22
22
|
items = queue.list_items(status=status, limit=limit)
|
|
@@ -69,20 +69,20 @@ def list_queue(
|
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
@app.command("retry")
|
|
72
|
-
def retry_item(queue_id: str = typer.Argument(..., help="Queue ID to retry")):
|
|
72
|
+
def retry_item(queue_id: str = typer.Argument(..., help="Queue ID to retry")) -> None:
|
|
73
73
|
"""Retry a failed queue item."""
|
|
74
74
|
queue = Queue()
|
|
75
75
|
item = queue.get_item(queue_id)
|
|
76
76
|
|
|
77
77
|
if not item:
|
|
78
78
|
console.print(f"[red]Queue item not found: {queue_id}[/red]")
|
|
79
|
-
raise typer.Exit(1)
|
|
79
|
+
raise typer.Exit(1) from None
|
|
80
80
|
|
|
81
81
|
if item.status != QueueStatus.FAILED:
|
|
82
82
|
console.print(
|
|
83
83
|
f"[yellow]Item {queue_id} is not failed (status: {item.status})[/yellow]"
|
|
84
84
|
)
|
|
85
|
-
raise typer.Exit(1)
|
|
85
|
+
raise typer.Exit(1) from None
|
|
86
86
|
|
|
87
87
|
# Reset to pending
|
|
88
88
|
queue.update_status(queue_id, QueueStatus.PENDING, error_message=None)
|
|
@@ -103,7 +103,7 @@ def clear_queue(
|
|
|
103
103
|
7, "--days", "-d", help="Clear items older than this many days"
|
|
104
104
|
),
|
|
105
105
|
confirm: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
|
|
106
|
-
):
|
|
106
|
+
) -> None:
|
|
107
107
|
"""Clear old queue items."""
|
|
108
108
|
queue = Queue()
|
|
109
109
|
|
|
@@ -115,7 +115,7 @@ def clear_queue(
|
|
|
115
115
|
|
|
116
116
|
if not typer.confirm(msg):
|
|
117
117
|
console.print("[yellow]Cancelled[/yellow]")
|
|
118
|
-
raise typer.Exit(0)
|
|
118
|
+
raise typer.Exit(0) from None
|
|
119
119
|
|
|
120
120
|
queue.cleanup_old(days=days)
|
|
121
121
|
console.print("[green]✓[/green] Cleared old queue items")
|
|
@@ -126,7 +126,7 @@ worker_app = typer.Typer(name="worker", help="Worker management commands")
|
|
|
126
126
|
|
|
127
127
|
|
|
128
128
|
@worker_app.command("start")
|
|
129
|
-
def start_worker():
|
|
129
|
+
def start_worker() -> None:
|
|
130
130
|
"""Start the background worker."""
|
|
131
131
|
manager = WorkerManager()
|
|
132
132
|
|
|
@@ -142,11 +142,11 @@ def start_worker():
|
|
|
142
142
|
console.print(f"PID: {status.get('pid')}")
|
|
143
143
|
else:
|
|
144
144
|
console.print("[red]✗[/red] Failed to start worker")
|
|
145
|
-
raise typer.Exit(1)
|
|
145
|
+
raise typer.Exit(1) from None
|
|
146
146
|
|
|
147
147
|
|
|
148
148
|
@worker_app.command("stop")
|
|
149
|
-
def stop_worker():
|
|
149
|
+
def stop_worker() -> None:
|
|
150
150
|
"""Stop the background worker."""
|
|
151
151
|
manager = WorkerManager()
|
|
152
152
|
|
|
@@ -158,11 +158,11 @@ def stop_worker():
|
|
|
158
158
|
console.print("[green]✓[/green] Worker stopped successfully")
|
|
159
159
|
else:
|
|
160
160
|
console.print("[red]✗[/red] Failed to stop worker")
|
|
161
|
-
raise typer.Exit(1)
|
|
161
|
+
raise typer.Exit(1) from None
|
|
162
162
|
|
|
163
163
|
|
|
164
164
|
@worker_app.command("restart")
|
|
165
|
-
def restart_worker():
|
|
165
|
+
def restart_worker() -> None:
|
|
166
166
|
"""Restart the background worker."""
|
|
167
167
|
manager = WorkerManager()
|
|
168
168
|
|
|
@@ -172,11 +172,11 @@ def restart_worker():
|
|
|
172
172
|
console.print(f"PID: {status.get('pid')}")
|
|
173
173
|
else:
|
|
174
174
|
console.print("[red]✗[/red] Failed to restart worker")
|
|
175
|
-
raise typer.Exit(1)
|
|
175
|
+
raise typer.Exit(1) from None
|
|
176
176
|
|
|
177
177
|
|
|
178
178
|
@worker_app.command("status")
|
|
179
|
-
def worker_status():
|
|
179
|
+
def worker_status() -> None:
|
|
180
180
|
"""Check worker status."""
|
|
181
181
|
manager = WorkerManager()
|
|
182
182
|
status = manager.get_status()
|
|
@@ -212,7 +212,7 @@ def worker_status():
|
|
|
212
212
|
def worker_logs(
|
|
213
213
|
lines: int = typer.Option(50, "--lines", "-n", help="Number of lines to show"),
|
|
214
214
|
follow: bool = typer.Option(False, "--follow", "-f", help="Follow log output"),
|
|
215
|
-
):
|
|
215
|
+
) -> None:
|
|
216
216
|
"""View worker logs."""
|
|
217
217
|
import time
|
|
218
218
|
from pathlib import Path
|
|
@@ -221,7 +221,7 @@ def worker_logs(
|
|
|
221
221
|
|
|
222
222
|
if not log_file.exists():
|
|
223
223
|
console.print("[yellow]No log file found[/yellow]")
|
|
224
|
-
raise typer.Exit(1)
|
|
224
|
+
raise typer.Exit(1) from None
|
|
225
225
|
|
|
226
226
|
if follow:
|
|
227
227
|
# Follow mode - like tail -f
|