steerdev 0.4.27__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.
Files changed (57) hide show
  1. steerdev-0.4.27.dist-info/METADATA +224 -0
  2. steerdev-0.4.27.dist-info/RECORD +57 -0
  3. steerdev-0.4.27.dist-info/WHEEL +4 -0
  4. steerdev-0.4.27.dist-info/entry_points.txt +2 -0
  5. steerdev_agent/__init__.py +10 -0
  6. steerdev_agent/api/__init__.py +32 -0
  7. steerdev_agent/api/activity.py +278 -0
  8. steerdev_agent/api/agents.py +145 -0
  9. steerdev_agent/api/client.py +158 -0
  10. steerdev_agent/api/commands.py +399 -0
  11. steerdev_agent/api/configs.py +238 -0
  12. steerdev_agent/api/context.py +306 -0
  13. steerdev_agent/api/events.py +294 -0
  14. steerdev_agent/api/hooks.py +178 -0
  15. steerdev_agent/api/implementation_plan.py +408 -0
  16. steerdev_agent/api/messages.py +231 -0
  17. steerdev_agent/api/prd.py +281 -0
  18. steerdev_agent/api/runs.py +526 -0
  19. steerdev_agent/api/sessions.py +403 -0
  20. steerdev_agent/api/specs.py +321 -0
  21. steerdev_agent/api/tasks.py +659 -0
  22. steerdev_agent/api/workflow_runs.py +351 -0
  23. steerdev_agent/api/workflows.py +191 -0
  24. steerdev_agent/cli.py +2254 -0
  25. steerdev_agent/config/__init__.py +19 -0
  26. steerdev_agent/config/models.py +236 -0
  27. steerdev_agent/config/platform.py +272 -0
  28. steerdev_agent/config/settings.py +62 -0
  29. steerdev_agent/daemon.py +675 -0
  30. steerdev_agent/executor/__init__.py +64 -0
  31. steerdev_agent/executor/base.py +121 -0
  32. steerdev_agent/executor/claude.py +328 -0
  33. steerdev_agent/executor/stream.py +163 -0
  34. steerdev_agent/git/__init__.py +1 -0
  35. steerdev_agent/handlers/__init__.py +5 -0
  36. steerdev_agent/handlers/prd.py +533 -0
  37. steerdev_agent/integration.py +334 -0
  38. steerdev_agent/prompt/__init__.py +10 -0
  39. steerdev_agent/prompt/builder.py +263 -0
  40. steerdev_agent/prompt/templates.py +422 -0
  41. steerdev_agent/py.typed +0 -0
  42. steerdev_agent/runner.py +829 -0
  43. steerdev_agent/setup/__init__.py +5 -0
  44. steerdev_agent/setup/claude_setup.py +560 -0
  45. steerdev_agent/setup/templates/claude_md_section.md +140 -0
  46. steerdev_agent/setup/templates/settings.json +69 -0
  47. steerdev_agent/setup/templates/skills/activity/SKILL.md +160 -0
  48. steerdev_agent/setup/templates/skills/context/SKILL.md +122 -0
  49. steerdev_agent/setup/templates/skills/git-workflow/SKILL.md +218 -0
  50. steerdev_agent/setup/templates/skills/progress-logging/SKILL.md +211 -0
  51. steerdev_agent/setup/templates/skills/specs-management/SKILL.md +161 -0
  52. steerdev_agent/setup/templates/skills/task-management/SKILL.md +343 -0
  53. steerdev_agent/setup/templates/steerdev.yaml +51 -0
  54. steerdev_agent/version.py +149 -0
  55. steerdev_agent/workflow/__init__.py +10 -0
  56. steerdev_agent/workflow/executor.py +494 -0
  57. steerdev_agent/workflow/memory.py +185 -0
@@ -0,0 +1,321 @@
1
+ """Specs API client for SteerDev Agent.
2
+
3
+ Provides methods for interacting with specification documents (PRDs)
4
+ through the CLI skill interface.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any
10
+
11
+ from pydantic import BaseModel, Field
12
+ from rich.console import Console
13
+ from rich.panel import Panel
14
+ from rich.table import Table
15
+
16
+ from steerdev_agent.api.client import SteerDevClient, get_project_id
17
+
18
+ console = Console()
19
+
20
+
21
+ # Valid spec statuses
22
+ VALID_SPEC_STATUSES = [
23
+ "draft",
24
+ "analyzing",
25
+ "clarifying",
26
+ "planning",
27
+ "ready",
28
+ "completed",
29
+ ]
30
+
31
+
32
+ class SpecDocument(BaseModel):
33
+ """Spec document model."""
34
+
35
+ id: str
36
+ project_id: str
37
+ title: str
38
+ content: str
39
+ source: str
40
+ source_file_name: str | None = None
41
+ status: str # draft, analyzing, clarifying, planning, ready, completed
42
+ implementation_plan: dict[str, Any] | None = None
43
+ run_id: str | None = None
44
+ created_by: str
45
+ created_at: str
46
+ updated_at: str
47
+ linear_identifier: str | None = Field(default=None, description="Linear issue ID")
48
+
49
+
50
+ class SpecsClient(SteerDevClient):
51
+ """Client for spec management operations.
52
+
53
+ Provides CRUD operations for specification documents (PRDs).
54
+ """
55
+
56
+ def get_spec(self, spec_id: str) -> dict[str, Any] | None:
57
+ """Get a spec document by ID.
58
+
59
+ Args:
60
+ spec_id: Spec document ID (UUID).
61
+
62
+ Returns:
63
+ Spec data dict or None if not found.
64
+ """
65
+ console.print(f"Fetching spec {spec_id} from {self.api_base}/prd/{spec_id}")
66
+ response = self.get(f"/prd/{spec_id}")
67
+
68
+ if response.status_code == 404:
69
+ return None
70
+
71
+ if response.status_code != 200:
72
+ console.print(f"[red]API Error: {response.status_code} - {response.text}[/red]")
73
+ return None
74
+
75
+ return response.json()
76
+
77
+ def list_specs(
78
+ self,
79
+ project_id: str | None = None,
80
+ status: str | None = None,
81
+ limit: int = 20,
82
+ ) -> list[dict[str, Any]]:
83
+ """List specs with optional filters.
84
+
85
+ Args:
86
+ project_id: Filter by project ID. Falls back to STEERDEV_PROJECT_ID env var.
87
+ status: Filter by status.
88
+ limit: Maximum number of specs to return.
89
+
90
+ Returns:
91
+ List of spec dicts.
92
+ """
93
+ effective_project_id = project_id or get_project_id()
94
+
95
+ params: dict[str, str | int] = {"limit": limit}
96
+ if status:
97
+ params["status"] = status
98
+ if effective_project_id:
99
+ params["project_id"] = effective_project_id
100
+
101
+ console.print(f"Fetching specs from {self.api_base}/prd")
102
+ response = self.get("/prd", params=params)
103
+
104
+ if response.status_code != 200:
105
+ console.print(f"[red]API Error: {response.status_code} - {response.text}[/red]")
106
+ return []
107
+
108
+ data = response.json()
109
+ return data.get("specs", data.get("documents", [])) if isinstance(data, dict) else data
110
+
111
+ def update_spec(
112
+ self,
113
+ spec_id: str,
114
+ content: str | None = None,
115
+ status: str | None = None,
116
+ title: str | None = None,
117
+ ) -> bool:
118
+ """Update a spec's fields.
119
+
120
+ Args:
121
+ spec_id: Spec ID to update.
122
+ content: New content (markdown).
123
+ status: New status.
124
+ title: New title.
125
+
126
+ Returns:
127
+ True if update succeeded.
128
+ """
129
+ payload: dict[str, str] = {}
130
+ if content:
131
+ payload["content"] = content
132
+ if status:
133
+ payload["status"] = status
134
+ if title:
135
+ payload["title"] = title
136
+
137
+ if not payload:
138
+ return False
139
+
140
+ console.print(f"Updating spec {spec_id} at {self.api_base}/prd/{spec_id}")
141
+ response = self.patch(f"/prd/{spec_id}", json=payload)
142
+
143
+ if response.status_code == 404:
144
+ console.print(f"[red]Error: Spec '{spec_id}' not found[/red]")
145
+ return False
146
+
147
+ if response.status_code == 400:
148
+ console.print(f"[red]Validation Error: {response.text}[/red]")
149
+ return False
150
+
151
+ if response.status_code not in (200, 204):
152
+ console.print(f"[red]API Error: {response.status_code} - {response.text}[/red]")
153
+ return False
154
+
155
+ return True
156
+
157
+ def create_spec(
158
+ self,
159
+ title: str,
160
+ content: str,
161
+ project_id: str | None = None,
162
+ source: str = "agent",
163
+ ) -> dict[str, Any] | None:
164
+ """Create a new spec document.
165
+
166
+ Args:
167
+ title: Spec title.
168
+ content: Spec content (markdown).
169
+ project_id: Project ID. Falls back to STEERDEV_PROJECT_ID env var.
170
+ source: Source of the spec (default: "agent").
171
+
172
+ Returns:
173
+ Created spec data dict or None on failure.
174
+ """
175
+ effective_project_id = project_id or get_project_id()
176
+ if not effective_project_id:
177
+ console.print(
178
+ "[red]Error: project_id is required. "
179
+ "Use --project-id or set STEERDEV_PROJECT_ID environment variable[/red]"
180
+ )
181
+ return None
182
+
183
+ payload = {
184
+ "project_id": effective_project_id,
185
+ "title": title,
186
+ "content": content,
187
+ "source": source,
188
+ }
189
+
190
+ console.print(f"Creating spec at {self.api_base}/prd")
191
+ response = self.post("/prd", json=payload)
192
+
193
+ if response.status_code not in (200, 201):
194
+ console.print(f"[red]API Error: {response.status_code} - {response.text}[/red]")
195
+ return None
196
+
197
+ return response.json()
198
+
199
+
200
+ def get_status_style(status: str) -> str:
201
+ """Get Rich style for a spec status.
202
+
203
+ Args:
204
+ status: Spec status value.
205
+
206
+ Returns:
207
+ Rich style string.
208
+ """
209
+ return {
210
+ "draft": "dim",
211
+ "analyzing": "yellow",
212
+ "clarifying": "cyan",
213
+ "planning": "blue",
214
+ "ready": "green",
215
+ "completed": "dim green",
216
+ }.get(status, "white")
217
+
218
+
219
+ def display_spec(spec: dict[str, Any], title: str = "Spec") -> None:
220
+ """Display a spec in a formatted panel.
221
+
222
+ Args:
223
+ spec: Spec data dict.
224
+ title: Panel title.
225
+ """
226
+ linear_id = spec.get("linear_identifier", "N/A")
227
+ status_style = get_status_style(spec.get("status", ""))
228
+
229
+ spec_info = (
230
+ f"[bold cyan]Linear ID:[/bold cyan] {linear_id}\n"
231
+ f"[bold cyan]ID:[/bold cyan] {spec.get('id', 'N/A')}\n"
232
+ f"[bold cyan]Title:[/bold cyan] {spec.get('title', 'N/A')}\n"
233
+ f"[bold cyan]Status:[/bold cyan] [{status_style}]{spec.get('status', 'N/A')}[/{status_style}]\n"
234
+ f"[bold cyan]Project ID:[/bold cyan] {spec.get('project_id', 'N/A')}\n"
235
+ f"[bold cyan]Source:[/bold cyan] {spec.get('source', 'N/A')}"
236
+ )
237
+
238
+ if spec.get("source_file_name"):
239
+ spec_info += f"\n[bold cyan]Source File:[/bold cyan] {spec['source_file_name']}"
240
+
241
+ if spec.get("content"):
242
+ # Truncate content for display, show first 500 chars
243
+ content = spec["content"]
244
+ if len(content) > 500:
245
+ content = content[:500] + "..."
246
+ spec_info += f"\n\n[bold cyan]Content:[/bold cyan]\n{content}"
247
+
248
+ if spec.get("run_id"):
249
+ spec_info += f"\n\n[bold cyan]Run ID:[/bold cyan] {spec['run_id']}"
250
+
251
+ console.print(Panel(spec_info, title=title, border_style="green"))
252
+
253
+
254
+ def display_spec_list(specs: list[dict[str, Any]], full_ids: bool = True) -> None:
255
+ """Display a list of specs in a formatted table.
256
+
257
+ Args:
258
+ specs: List of spec data dicts.
259
+ full_ids: If True, show full UUIDs. If False, truncate to 8 chars.
260
+ """
261
+ if not specs:
262
+ console.print("[yellow]No specs found[/yellow]")
263
+ return
264
+
265
+ table = Table(title="Specs")
266
+ table.add_column("Linear ID", style="cyan", no_wrap=True)
267
+ table.add_column("Title", style="white")
268
+ table.add_column("Status", style="magenta")
269
+ table.add_column("Source", style="dim")
270
+ table.add_column("ID", style="dim")
271
+
272
+ for spec in specs:
273
+ status_style = get_status_style(spec.get("status", ""))
274
+ spec_id = str(spec.get("id", "N/A"))
275
+ linear_id = spec.get("linear_identifier", "N/A")
276
+
277
+ if not full_ids:
278
+ spec_id = spec_id[:8] + "..." if len(spec_id) > 8 else spec_id
279
+
280
+ table.add_row(
281
+ linear_id,
282
+ spec.get("title", "N/A")[:50],
283
+ f"[{status_style}]{spec.get('status', 'N/A')}[/{status_style}]",
284
+ spec.get("source", "N/A"),
285
+ spec_id,
286
+ )
287
+
288
+ console.print(table)
289
+ console.print(f"\n[dim]Total: {len(specs)} specs[/dim]")
290
+
291
+
292
+ def display_spec_update_success(
293
+ spec_id: str,
294
+ content: str | None = None,
295
+ status: str | None = None,
296
+ title: str | None = None,
297
+ ) -> None:
298
+ """Display update success message.
299
+
300
+ Args:
301
+ spec_id: Updated spec ID.
302
+ content: Updated content if any.
303
+ status: Updated status if any.
304
+ title: Updated title if any.
305
+ """
306
+ updates = []
307
+ if content:
308
+ updates.append("Content updated")
309
+ if status:
310
+ updates.append(f"Status -> {status}")
311
+ if title:
312
+ updates.append("Title updated")
313
+
314
+ console.print(
315
+ Panel(
316
+ f"[bold green]Spec {spec_id} updated[/bold green]\n\n"
317
+ + "\n".join(f"* {u}" for u in updates),
318
+ title="Success",
319
+ border_style="green",
320
+ )
321
+ )