bumblebee-cli 0.1.0 → 0.2.0

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 (38) hide show
  1. package/README.md +49 -47
  2. package/bin/bb.mjs +132 -102
  3. package/package.json +28 -28
  4. package/python/bb_cli/__main__.py +3 -0
  5. package/python/bb_cli/api_client.py +7 -0
  6. package/python/bb_cli/commands/agent.py +2287 -1030
  7. package/python/bb_cli/commands/auth.py +79 -79
  8. package/python/bb_cli/commands/board.py +47 -47
  9. package/python/bb_cli/commands/comment.py +34 -34
  10. package/python/bb_cli/commands/daemon.py +153 -0
  11. package/python/bb_cli/commands/init.py +83 -62
  12. package/python/bb_cli/commands/item.py +192 -192
  13. package/python/bb_cli/commands/project.py +175 -111
  14. package/python/bb_cli/config.py +136 -136
  15. package/python/bb_cli/main.py +44 -44
  16. package/python/bb_cli/progress.py +117 -0
  17. package/python/bb_cli/streaming.py +168 -0
  18. package/python/pyproject.toml +1 -1
  19. package/python/{bb_cli/bumblebee_cli.egg-info/requires.txt → requirements.txt} +4 -4
  20. package/scripts/build.sh +20 -20
  21. package/scripts/postinstall.mjs +146 -121
  22. package/python/bb_cli/bumblebee_cli.egg-info/PKG-INFO +0 -9
  23. package/python/bb_cli/bumblebee_cli.egg-info/SOURCES.txt +0 -21
  24. package/python/bb_cli/bumblebee_cli.egg-info/dependency_links.txt +0 -1
  25. package/python/bb_cli/bumblebee_cli.egg-info/entry_points.txt +0 -2
  26. package/python/bb_cli/bumblebee_cli.egg-info/top_level.txt +0 -5
  27. package/python/bb_cli/commands/__pycache__/__init__.cpython-313.pyc +0 -0
  28. package/python/bb_cli/commands/__pycache__/agent.cpython-313.pyc +0 -0
  29. package/python/bb_cli/commands/__pycache__/auth.cpython-313.pyc +0 -0
  30. package/python/bb_cli/commands/__pycache__/board.cpython-313.pyc +0 -0
  31. package/python/bb_cli/commands/__pycache__/comment.cpython-313.pyc +0 -0
  32. package/python/bb_cli/commands/__pycache__/init.cpython-313.pyc +0 -0
  33. package/python/bb_cli/commands/__pycache__/item.cpython-313.pyc +0 -0
  34. package/python/bb_cli/commands/__pycache__/label.cpython-313.pyc +0 -0
  35. package/python/bb_cli/commands/__pycache__/project.cpython-313.pyc +0 -0
  36. package/python/bb_cli/commands/__pycache__/sprint.cpython-313.pyc +0 -0
  37. package/python/bb_cli/commands/__pycache__/story.cpython-313.pyc +0 -0
  38. package/python/bb_cli/commands/__pycache__/task.cpython-313.pyc +0 -0
@@ -1,192 +1,192 @@
1
- import re
2
-
3
- import typer
4
- from rich import print as rprint
5
- from rich.table import Table
6
-
7
- from ..api_client import api_get, api_post, api_put
8
- from ..config import get_current_project
9
-
10
- app = typer.Typer(help="Work item management")
11
-
12
-
13
- def _require_project() -> str:
14
- slug = get_current_project()
15
- if not slug:
16
- rprint("[red]No project selected. Run [bold]bb project switch <slug>[/bold] first.[/red]")
17
- raise typer.Exit(1)
18
- return slug
19
-
20
-
21
- def _resolve_item(slug: str, id_or_number: str) -> dict:
22
- """Resolve a work item by UUID, number, or KEY-number format."""
23
- # UUID format
24
- if len(id_or_number) > 8 and "-" in id_or_number:
25
- return api_get(f"/api/work-items/{id_or_number}")
26
-
27
- # KEY-number format (e.g. BB-42) or plain integer
28
- match = re.match(r"^(?:[A-Za-z]+-)?(\d+)$", id_or_number)
29
- if match:
30
- number = int(match.group(1))
31
- return api_get(f"/api/projects/{slug}/work-items/by-number/{number}")
32
-
33
- rprint(f"[red]Invalid identifier: {id_or_number}. Use UUID, number, or KEY-number (e.g. BB-42).[/red]")
34
- raise typer.Exit(1)
35
-
36
-
37
- @app.command(name="list")
38
- def list_items(
39
- type: str = typer.Option(None, "-t", "--type", help="Filter by type (story, task, epic, bug...)"),
40
- status: str = typer.Option(None, "-s", "--status", help="Filter by status"),
41
- parent: str = typer.Option(None, "--parent", help="Filter by parent ID"),
42
- assignee: str = typer.Option(None, "-a", "--assignee", help="Filter by assignee"),
43
- ):
44
- """List work items in the current project."""
45
- slug = _require_project()
46
- params = {}
47
- if type:
48
- params["type"] = type
49
- if status:
50
- params["status"] = status
51
- if parent:
52
- params["parent_id"] = parent
53
- if assignee:
54
- params["assignee"] = assignee
55
- items = api_get(f"/api/projects/{slug}/work-items", params=params)
56
- table = Table(title=f"Work Items - {slug}")
57
- table.add_column("#", style="cyan", justify="right")
58
- table.add_column("Type", style="magenta")
59
- table.add_column("Title")
60
- table.add_column("Status", style="green")
61
- table.add_column("Priority")
62
- table.add_column("Assignee")
63
- for item in items:
64
- key = item.get("key") or str(item["number"])
65
- table.add_row(
66
- key,
67
- item["type"],
68
- item["title"],
69
- item["status"],
70
- item["priority"],
71
- item.get("assignee") or "-",
72
- )
73
- rprint(table)
74
-
75
-
76
- @app.command()
77
- def create(
78
- title: str = typer.Argument(...),
79
- type: str = typer.Option("story", "-t", "--type", help="Item type: story, task, epic, bug, feature, chore, spike"),
80
- description: str = typer.Option(None, "-d", "--description"),
81
- priority: str = typer.Option("medium", "-p", "--priority"),
82
- parent: str = typer.Option(None, "--parent", help="Parent item ID or number"),
83
- ):
84
- """Create a new work item."""
85
- slug = _require_project()
86
- data: dict = {"title": title, "type": type, "priority": priority}
87
- if description:
88
- data["description"] = description
89
- if parent:
90
- # Resolve parent to UUID
91
- parent_item = _resolve_item(slug, parent)
92
- data["parent_id"] = parent_item["id"]
93
- item = api_post(f"/api/projects/{slug}/work-items", json=data)
94
- key = item.get("key") or f"#{item['number']}"
95
- rprint(f"[green]Created {item['type']} {key}: {item['title']}[/green]")
96
-
97
-
98
- @app.command()
99
- def show(id_or_number: str = typer.Argument(..., help="UUID, number, or KEY-number (e.g. BB-42)")):
100
- """Show work item details."""
101
- slug = _require_project()
102
- item = _resolve_item(slug, id_or_number)
103
- key = item.get("key") or f"#{item['number']}"
104
- rprint(f"[bold]{key}[/bold] [{item['type']}] {item['title']}")
105
- rprint(f"Status: {item['status']} | Priority: {item['priority']}")
106
- if item.get("assignee"):
107
- rprint(f"Assignee: {item['assignee']}")
108
- if item.get("parent_id"):
109
- rprint(f"Parent: {item['parent_id']}")
110
- if item.get("sprint_id"):
111
- rprint(f"Sprint: {item['sprint_id']}")
112
- if item.get("story_points"):
113
- rprint(f"Points: {item['story_points']}")
114
- if item.get("description"):
115
- rprint(f"\n{item['description']}")
116
- if item.get("acceptance_criteria"):
117
- rprint(f"\n[bold]Acceptance Criteria:[/bold]\n{item['acceptance_criteria']}")
118
- if item.get("plan"):
119
- rprint(f"\n[bold]Plan:[/bold]\n{item['plan']}")
120
- if item.get("ai_summary"):
121
- rprint(f"\n[bold]AI Summary:[/bold]\n{item['ai_summary']}")
122
-
123
-
124
- @app.command()
125
- def update(
126
- id_or_number: str = typer.Argument(..., help="UUID, number, or KEY-number"),
127
- status: str = typer.Option(None, "-s", "--status"),
128
- assignee: str = typer.Option(None, "-a", "--assignee"),
129
- priority: str = typer.Option(None, "-p", "--priority"),
130
- title: str = typer.Option(None, "--title"),
131
- type: str = typer.Option(None, "-t", "--type"),
132
- ):
133
- """Update a work item."""
134
- slug = _require_project()
135
- item = _resolve_item(slug, id_or_number)
136
- data = {}
137
- if status:
138
- data["status"] = status
139
- if assignee:
140
- data["assignee"] = assignee
141
- if priority:
142
- data["priority"] = priority
143
- if title:
144
- data["title"] = title
145
- if type:
146
- data["type"] = type
147
- if not data:
148
- rprint("[yellow]Nothing to update.[/yellow]")
149
- raise typer.Exit()
150
- updated = api_put(f"/api/work-items/{item['id']}", json=data)
151
- key = updated.get("key") or f"#{updated['number']}"
152
- rprint(f"[green]Updated {key}[/green]")
153
-
154
-
155
- @app.command()
156
- def assign(
157
- id_or_number: str = typer.Argument(..., help="UUID, number, or KEY-number"),
158
- assignee: str = typer.Argument(...),
159
- ):
160
- """Assign a work item to someone."""
161
- slug = _require_project()
162
- item = _resolve_item(slug, id_or_number)
163
- updated = api_put(f"/api/work-items/{item['id']}", json={"assignee": assignee})
164
- key = updated.get("key") or f"#{updated['number']}"
165
- rprint(f"[green]{key} assigned to {assignee}[/green]")
166
-
167
-
168
- @app.command()
169
- def children(id_or_number: str = typer.Argument(..., help="UUID, number, or KEY-number")):
170
- """List children of a work item."""
171
- slug = _require_project()
172
- item = _resolve_item(slug, id_or_number)
173
- kids = api_get(f"/api/work-items/{item['id']}/children")
174
- if not kids:
175
- rprint("[dim]No children.[/dim]")
176
- return
177
- table = Table(title=f"Children of {item.get('key') or '#' + str(item['number'])}")
178
- table.add_column("#", style="cyan", justify="right")
179
- table.add_column("Type", style="magenta")
180
- table.add_column("Title")
181
- table.add_column("Status", style="green")
182
- table.add_column("Assignee")
183
- for kid in kids:
184
- key = kid.get("key") or str(kid["number"])
185
- table.add_row(
186
- key,
187
- kid["type"],
188
- kid["title"],
189
- kid["status"],
190
- kid.get("assignee") or "-",
191
- )
192
- rprint(table)
1
+ import re
2
+
3
+ import typer
4
+ from rich import print as rprint
5
+ from rich.table import Table
6
+
7
+ from ..api_client import api_get, api_post, api_put
8
+ from ..config import get_current_project
9
+
10
+ app = typer.Typer(help="Work item management")
11
+
12
+
13
+ def _require_project() -> str:
14
+ slug = get_current_project()
15
+ if not slug:
16
+ rprint("[red]No project selected. Run [bold]bb project switch <slug>[/bold] first.[/red]")
17
+ raise typer.Exit(1)
18
+ return slug
19
+
20
+
21
+ def _resolve_item(slug: str, id_or_number: str) -> dict:
22
+ """Resolve a work item by UUID, number, or KEY-number format."""
23
+ # UUID format
24
+ if len(id_or_number) > 8 and "-" in id_or_number:
25
+ return api_get(f"/api/work-items/{id_or_number}")
26
+
27
+ # KEY-number format (e.g. BB-42) or plain integer
28
+ match = re.match(r"^(?:[A-Za-z]+-)?(\d+)$", id_or_number)
29
+ if match:
30
+ number = int(match.group(1))
31
+ return api_get(f"/api/projects/{slug}/work-items/by-number/{number}")
32
+
33
+ rprint(f"[red]Invalid identifier: {id_or_number}. Use UUID, number, or KEY-number (e.g. BB-42).[/red]")
34
+ raise typer.Exit(1)
35
+
36
+
37
+ @app.command(name="list")
38
+ def list_items(
39
+ type: str = typer.Option(None, "-t", "--type", help="Filter by type (story, task, epic, bug...)"),
40
+ status: str = typer.Option(None, "-s", "--status", help="Filter by status"),
41
+ parent: str = typer.Option(None, "--parent", help="Filter by parent ID"),
42
+ assignee: str = typer.Option(None, "-a", "--assignee", help="Filter by assignee"),
43
+ ):
44
+ """List work items in the current project."""
45
+ slug = _require_project()
46
+ params = {}
47
+ if type:
48
+ params["type"] = type
49
+ if status:
50
+ params["status"] = status
51
+ if parent:
52
+ params["parent_id"] = parent
53
+ if assignee:
54
+ params["assignee"] = assignee
55
+ items = api_get(f"/api/projects/{slug}/work-items", params=params)
56
+ table = Table(title=f"Work Items - {slug}")
57
+ table.add_column("#", style="cyan", justify="right")
58
+ table.add_column("Type", style="magenta")
59
+ table.add_column("Title")
60
+ table.add_column("Status", style="green")
61
+ table.add_column("Priority")
62
+ table.add_column("Assignee")
63
+ for item in items:
64
+ key = item.get("key") or str(item["number"])
65
+ table.add_row(
66
+ key,
67
+ item["type"],
68
+ item["title"],
69
+ item["status"],
70
+ item["priority"],
71
+ item.get("assignee") or "-",
72
+ )
73
+ rprint(table)
74
+
75
+
76
+ @app.command()
77
+ def create(
78
+ title: str = typer.Argument(...),
79
+ type: str = typer.Option("story", "-t", "--type", help="Item type: story, task, epic, bug, feature, chore, spike"),
80
+ description: str = typer.Option(None, "-d", "--description"),
81
+ priority: str = typer.Option("medium", "-p", "--priority"),
82
+ parent: str = typer.Option(None, "--parent", help="Parent item ID or number"),
83
+ ):
84
+ """Create a new work item."""
85
+ slug = _require_project()
86
+ data: dict = {"title": title, "type": type, "priority": priority}
87
+ if description:
88
+ data["description"] = description
89
+ if parent:
90
+ # Resolve parent to UUID
91
+ parent_item = _resolve_item(slug, parent)
92
+ data["parent_id"] = parent_item["id"]
93
+ item = api_post(f"/api/projects/{slug}/work-items", json=data)
94
+ key = item.get("key") or f"#{item['number']}"
95
+ rprint(f"[green]Created {item['type']} {key}: {item['title']}[/green]")
96
+
97
+
98
+ @app.command()
99
+ def show(id_or_number: str = typer.Argument(..., help="UUID, number, or KEY-number (e.g. BB-42)")):
100
+ """Show work item details."""
101
+ slug = _require_project()
102
+ item = _resolve_item(slug, id_or_number)
103
+ key = item.get("key") or f"#{item['number']}"
104
+ rprint(f"[bold]{key}[/bold] [{item['type']}] {item['title']}")
105
+ rprint(f"Status: {item['status']} | Priority: {item['priority']}")
106
+ if item.get("assignee"):
107
+ rprint(f"Assignee: {item['assignee']}")
108
+ if item.get("parent_id"):
109
+ rprint(f"Parent: {item['parent_id']}")
110
+ if item.get("sprint_id"):
111
+ rprint(f"Sprint: {item['sprint_id']}")
112
+ if item.get("story_points"):
113
+ rprint(f"Points: {item['story_points']}")
114
+ if item.get("description"):
115
+ rprint(f"\n{item['description']}")
116
+ if item.get("acceptance_criteria"):
117
+ rprint(f"\n[bold]Acceptance Criteria:[/bold]\n{item['acceptance_criteria']}")
118
+ if item.get("plan"):
119
+ rprint(f"\n[bold]Plan:[/bold]\n{item['plan']}")
120
+ if item.get("ai_summary"):
121
+ rprint(f"\n[bold]AI Summary:[/bold]\n{item['ai_summary']}")
122
+
123
+
124
+ @app.command()
125
+ def update(
126
+ id_or_number: str = typer.Argument(..., help="UUID, number, or KEY-number"),
127
+ status: str = typer.Option(None, "-s", "--status"),
128
+ assignee: str = typer.Option(None, "-a", "--assignee"),
129
+ priority: str = typer.Option(None, "-p", "--priority"),
130
+ title: str = typer.Option(None, "--title"),
131
+ type: str = typer.Option(None, "-t", "--type"),
132
+ ):
133
+ """Update a work item."""
134
+ slug = _require_project()
135
+ item = _resolve_item(slug, id_or_number)
136
+ data = {}
137
+ if status:
138
+ data["status"] = status
139
+ if assignee:
140
+ data["assignee"] = assignee
141
+ if priority:
142
+ data["priority"] = priority
143
+ if title:
144
+ data["title"] = title
145
+ if type:
146
+ data["type"] = type
147
+ if not data:
148
+ rprint("[yellow]Nothing to update.[/yellow]")
149
+ raise typer.Exit()
150
+ updated = api_put(f"/api/work-items/{item['id']}", json=data)
151
+ key = updated.get("key") or f"#{updated['number']}"
152
+ rprint(f"[green]Updated {key}[/green]")
153
+
154
+
155
+ @app.command()
156
+ def assign(
157
+ id_or_number: str = typer.Argument(..., help="UUID, number, or KEY-number"),
158
+ assignee: str = typer.Argument(...),
159
+ ):
160
+ """Assign a work item to someone."""
161
+ slug = _require_project()
162
+ item = _resolve_item(slug, id_or_number)
163
+ updated = api_put(f"/api/work-items/{item['id']}", json={"assignee": assignee})
164
+ key = updated.get("key") or f"#{updated['number']}"
165
+ rprint(f"[green]{key} assigned to {assignee}[/green]")
166
+
167
+
168
+ @app.command()
169
+ def children(id_or_number: str = typer.Argument(..., help="UUID, number, or KEY-number")):
170
+ """List children of a work item."""
171
+ slug = _require_project()
172
+ item = _resolve_item(slug, id_or_number)
173
+ kids = api_get(f"/api/work-items/{item['id']}/children")
174
+ if not kids:
175
+ rprint("[dim]No children.[/dim]")
176
+ return
177
+ table = Table(title=f"Children of {item.get('key') or '#' + str(item['number'])}")
178
+ table.add_column("#", style="cyan", justify="right")
179
+ table.add_column("Type", style="magenta")
180
+ table.add_column("Title")
181
+ table.add_column("Status", style="green")
182
+ table.add_column("Assignee")
183
+ for kid in kids:
184
+ key = kid.get("key") or str(kid["number"])
185
+ table.add_row(
186
+ key,
187
+ kid["type"],
188
+ kid["title"],
189
+ kid["status"],
190
+ kid.get("assignee") or "-",
191
+ )
192
+ rprint(table)